沉淀再出发:java中的CAS和ABA问题整理

一、前言

在多并发程序设计之中,我们不得不面对并发、互斥、竞争、死锁、资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切,同样的也有了谁读谁写,这样的顺序和主次问题,于是就有了上锁,乐观锁和悲观锁,同步和异步,睡眠和换入换出等问题,归根到底就是模拟了社会上的分工协作与资源共享和抢占,要理解好这些现象的本质,我们需要更加深刻地进行类比和辨析,要知道这些内容的本质就是内存和CPU之间的故事,有的时候还会有一些外存或者其他缓存。只有我们深刻的理解了这些内容背后的原理和本质,我们才能算是真正的有所感悟,于是我们就需要理解操作系统的保护模式和进程线程的运行机理,需要理解计算机组成的基本原理,明白硬件的基本结构,运行的基本单位,存储和寄存器等多种电器元件,在理解了软件和硬件的基础之上,我们还要有一些编译原理方面的知识,因为编译器将我们能看到的程序语言翻译成了一个个中间代码直到机器码,只有明白了这一点才会知道i++,这样的简单的代码其实是有两三条指令才能完成的,并不是原子操作,明白了这些我们才能够真正的理解多并发机制。

二、java的CAS原理

2.1、CAS本质

java的CAS是Compare And Swap的缩写,先进行比较再进行交换,是实现java乐观锁的一种机制。java.util.concurrent包完全建立在CAS之上的,借助CAS实现了区别于synchronouse同步锁的一种乐观锁。但是这种机制是有一定的问题的,会造成ABA问题,因此需要加时间戳(版本号)这样的机制,为什么会有悲观锁和乐观锁呢,本质上如果使用一个进程把一个资源完全锁住就称为悲观锁,直到这个进程把资源使用完之后才能解锁,这样的上锁会导致其他进程在这段时间之内一直等不到这个内存资源,但是在实际的运行之中很多进程使用内存资源都是用来读操作的,并不会修改这个内存的内容,基于这样的实际情况,如果全部使用悲观锁,在高并发的环境下必定是非常浪费时间和CPU,内存资源的,因此类似CAS这样的乐观锁就出现了,主要是满足大部分进程用来读,少部分进程用来写这样的需求的。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

也就是什么意思呢??让我们想一下,一个内存地址上可以存一个变量,如果不对这个变量进行悲观锁(同步)的加锁方式,我们在面对着很多进程的读操作的时候肯定是没问题的,毕竟内存号称“一次写入,无数次读取”的,但是如果是写操作的时候,我们就要采取一定的办法保证如果之前已经有进程修改过这块空间的值了,这次我们的修改就不能继续下去,不然的话就会造成混乱了,因此我们需要自旋等待下次的操作时机,正是因为这样的操作机制,我们对内存的使用效率也有了很大的提高。那么我们怎么判断呢?我们知道内存的地址V,并且采取的轮询的机制,在这个机制之下,我们首先读取一次V的值,记为A,之后我们运行CAS算法,这个算法是一个原子操作,为什么不直接把我们需要写入的数值B写入内存呢?!原因很简单,那就是在我们读取了V的值A之后,假如此时发生了进程切换,这个内存空间被另一个进程修改了,如果此时我们直接写入B,就把上一个进程的值给取代了,上一个进程就丢失了该信息。因此,我们在下一步需要判断一下是不是有这种操作发生,如果有的话什么都不做,继续下一轮的读V的值A1,继续比较;如果值没有变,就姑且认为一切状态没发生变化,使用原子操作,比较并且替代这个内存的值。在这种机理之下,就要求这个内存V是唯一的,也就是volatile(易变)的,只在内存中有一份,在其他地方不能被缓存。这就是CAS的本质思想。但是大家觉得有什么不妥的吗?

2.2、CAS的问题

@1、CAS容易造成ABA问题。ABA:一个线程将某一内存地址中的数值A改成了B,接着又改成了A,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在java5中,已经提供了AtomicStampedReference来解决问题。
    CAS操作容易导致ABA问题,也就是在做i++之间,i可能被多个线程修改过了,只不过回到了最初的值,这时CAS会认为i的值没有变。i在外面逛了一圈回来,你能保证它没有做任何坏事,不能!!也许它把b的值减了一下,把c的值加了一下等等,更有甚者如果i是一个对象,这个对象有可能是新创建出来的,i是一个引用情况又如何,所以这里面还是存在着很多问题的,解决ABA问题的方法有很多,可以考虑增加一个修改计数,只有修改计数不变的且i值不变的情况下才做i++,也可以考虑引入版本号,当版本号相同时才做i++操作等,这和事务原子性处理有点类似。
    @2、CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。
    @3、会增加程序测试的复杂度,稍不注意就会出现问题。

@4、只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。

2.3、ABA的解决办法

如果一开始位置V得到的旧值是A,当进行赋值操作时再次读取发现仍然是A,并不能说明变量没有被其它线程改变过。有可能是其它线程将变量改为了B,后来又改回了A。大部分情况下ABA问题不会影响程序并发的正确性,如果要解决ABA问题,用传统的互斥同步可能比原子类更高效。可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。
    ABA问题的解决办法:
    1.在变量前面追加版本号:每次变量更新就把版本号加1,则A-B-A就变成1A-2B-3A。
    2.atomic包下的AtomicStampedReference类:其compareAndSet方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用的该标志的值设置为给定的更新值。

三、具体例子

3.1、JAVA中CAS的实现

JAVA中的CAS主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平台的汇编指令,目前的处理器基本都支持CAS,只不过不同的厂家的实现不一样罢了。
   Unsafe提供了三个方法用于CAS操作,分别是:

 public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);
public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
     value 表示 需要操作的对象
valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取)
expect 表示更新时value的期待值
update 表示将要更新的值

具体过程为每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false。
    Unsafe类中compareAndSwapInt的具体实现:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) 
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

3.2、AtomicInteger类中实现CAS的方法

 /*
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/ /*
*
*
*
*
*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/ package java.util.concurrent.atomic;
import java.util.function.IntUnaryOperator;
import java.util.function.IntBinaryOperator;
import sun.misc.Unsafe; /**
* An {@code int} value that may be updated atomically. See the
* {@link java.util.concurrent.atomic} package specification for
* description of the properties of atomic variables. An
* {@code AtomicInteger} is used in applications such as atomically
* incremented counters, and cannot be used as a replacement for an
* {@link java.lang.Integer}. However, this class does extend
* {@code Number} to allow uniform access by tools and utilities that
* deal with numerically-based classes.
*
* @since 1.5
* @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
} private volatile int value; /**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
} /**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
} /**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
} /**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set(int newValue) {
value = newValue;
} /**
* Eventually sets to the given value.
*
* @param newValue the new value
* @since 1.6
*/
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
} /**
* Atomically sets to the given value and returns the old value.
*
* @param newValue the new value
* @return the previous value
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
} /**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
} /**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
} /**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
} /**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
} /**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
} /**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
} /**
* Atomically decrements by one the current value.
*
* @return the updated value
*/
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
} /**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
} /**
* Atomically updates the current value with the results of
* applying the given function, returning the previous value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the previous value
* @since 1.8
*/
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
} /**
* Atomically updates the current value with the results of
* applying the given function, returning the updated value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the updated value
* @since 1.8
*/
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
} /**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the previous value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the previous value
* @since 1.8
*/
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
} /**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the updated value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the updated value
* @since 1.8
*/
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
} /**
* Returns the String representation of the current value.
* @return the String representation of the current value
*/
public String toString() {
return Integer.toString(get());
} /**
* Returns the value of this {@code AtomicInteger} as an {@code int}.
*/
public int intValue() {
return get();
} /**
* Returns the value of this {@code AtomicInteger} as a {@code long}
* after a widening primitive conversion.
* @jls 5.1.2 Widening Primitive Conversions
*/
public long longValue() {
return (long)get();
} /**
* Returns the value of this {@code AtomicInteger} as a {@code float}
* after a widening primitive conversion.
* @jls 5.1.2 Widening Primitive Conversions
*/
public float floatValue() {
return (float)get();
} /**
* Returns the value of this {@code AtomicInteger} as a {@code double}
* after a widening primitive conversion.
* @jls 5.1.2 Widening Primitive Conversions
*/
public double doubleValue() {
return (double)get();
} }

AtomicInteger代码

 public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + ;
if (compareAndSet(current, next))
return next;
}
}
public final int decrementAndGet() {
for (;;) {
int current = get();
int next = current - ;
if (compareAndSet(current, next))
return next;
}
}

但是上面的操作会造成CAS的ABA问题,基本是这个样子:

     进程P1在共享变量中读到值为A
P1被抢占了,进程P2执行
P2把共享变量里的值从A改成了B,再改回到A,此时被P1抢占。
P1回来看到共享变量里的值没有被改变,于是继续执行。

虽然P1以为变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。ABA问题最容易发生在lock free 的算法中的,CAS首当其冲,因为CAS判断的是指针的地址。如果这个地址被重用了呢,问题就很大了。(地址被重用是很经常发生的,一个内存分配后释放了,再分配,很有可能还是原来的地址)。比如上述的DeQueue()函数,因为我们要让head和tail分开,所以我们引入了一个dummy指针给head,当我们做CAS的之前,如果head的那块内存被回收并被重用了,而重用的内存又被EnQueue()进来了,这会有很大的问题。(内存管理中重用内存基本上是一种很常见的行为)
     具体案例:

 package com.cas.aba;

 import java.util.concurrent.atomic.AtomicInteger;

 public class AtomicIntegerTest {
public static AtomicInteger a = new AtomicInteger(1); public static void main(String[] args) {
Thread main = new Thread(() -> {
System.out.println("操作线程" + Thread.currentThread() + ",初始值 = " + a);
// 定义变量 a = 1
try {
Thread.sleep(1000);
// 等待1秒 ,以便让干扰线程执行
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isCASSuccess = a.compareAndSet(1, 2);
// CAS操作
System.out.println("操作线程" + Thread.currentThread() + ",CAS操作结果: " + isCASSuccess);
}, "主操作线程"); Thread other = new Thread(() -> {
Thread.yield();
// 确保thread-main线程优先执行
a.incrementAndGet(); // a 加 1, a + 1 = 1 + 1 = 2
System.out.println("操作线程" + Thread.currentThread() + ",【increment】 ,值 = " + a);
a.decrementAndGet(); // a 减 1, a - 1 = 2 - 1 = 1
System.out.println("操作线程" + Thread.currentThread() + ",【decrement】 ,值 = " + a);
}, "干扰线程");
main.start();
other.start();
}
}

可以看到发生了ABA,但是程序还是执行了CAS。

3.3、AtomicStampedReference类解决ABA问题:

/*
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/ /*
*
*
*
*
*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/ package java.util.concurrent.atomic; /**
* An {@code AtomicStampedReference} maintains an object reference
* along with an integer "stamp", that can be updated atomically.
*
* <p>Implementation note: This implementation maintains stamped
* references by creating internal objects representing "boxed"
* [reference, integer] pairs.
*
* @since 1.5
* @author Doug Lea
* @param <V> The type of object referred to by this reference
*/
public class AtomicStampedReference<V> { private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
} private volatile Pair<V> pair; /**
* Creates a new {@code AtomicStampedReference} with the given
* initial values.
*
* @param initialRef the initial reference
* @param initialStamp the initial stamp
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
} /**
* Returns the current value of the reference.
*
* @return the current value of the reference
*/
public V getReference() {
return pair.reference;
} /**
* Returns the current value of the stamp.
*
* @return the current value of the stamp
*/
public int getStamp() {
return pair.stamp;
} /**
* Returns the current values of both the reference and the stamp.
* Typical usage is {@code int[1] holder; ref = v.get(holder); }.
*
* @param stampHolder an array of size of at least one. On return,
* {@code stampholder[0]} will hold the value of the stamp.
* @return the current value of the reference
*/
public V get(int[] stampHolder) {
Pair<V> pair = this.pair;
stampHolder[0] = pair.stamp;
return pair.reference;
} /**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean weakCompareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
return compareAndSet(expectedReference, newReference,
expectedStamp, newStamp);
} /**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
} /**
* Unconditionally sets the value of both the reference and stamp.
*
* @param newReference the new value for the reference
* @param newStamp the new value for the stamp
*/
public void set(V newReference, int newStamp) {
Pair<V> current = pair;
if (newReference != current.reference || newStamp != current.stamp)
this.pair = Pair.of(newReference, newStamp);
} /**
* Atomically sets the value of the stamp to the given update value
* if the current reference is {@code ==} to the expected
* reference. Any given invocation of this operation may fail
* (return {@code false}) spuriously, but repeated invocation
* when the current value holds the expected value and no other
* thread is also attempting to set the value will eventually
* succeed.
*
* @param expectedReference the expected value of the reference
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean attemptStamp(V expectedReference, int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
(newStamp == current.stamp ||
casPair(current, Pair.of(expectedReference, newStamp)));
} // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset =
objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
} static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
String field, Class<?> klazz) {
try {
return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
} catch (NoSuchFieldException e) {
// Convert Exception to corresponding Error
NoSuchFieldError error = new NoSuchFieldError(field);
error.initCause(e);
throw error;
}
}
}

AtomicStampedReference代码

AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。

 /**
* 原子更新带有版本号的引用类型。
* 该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号。
* 可以解决使用CAS进行原子更新时,可能出现的ABA问题。
*/
public class AtomicStampedReference<V> { private static class Pair<T> {
final T reference;
//最好不要重复的一个数据,决定数据是否能设置成功
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
//根据reference和stamp来生成一个Pair的实例
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
//对Value 进行封装,放入Pair里面
private volatile Pair<V> pair; /**
* 返回当前的对象
*/
public V getReference() {
return pair.reference;
} /**
* 返回当前的stamp
*/
public int getStamp() {
return pair.stamp;
} /**
* 当当前的引用数据和期望的引用数据相等并且当前stamp和期望的stamp也相等
* 并且
* (当前的引用数据和新的引用数据相等并且当前stamp和新的stamp也相等
* 或者cas操作成功
* )
* @param 期望(老的)的引用数据
* @param 新的引用数据
* @param 期望(老的)的stamp值
* @param 新的stamp值
* @return
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
} /**
* 当前值和期望值比较设置
*/
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
}

   实验案例:

 package com.cas.aba;

 import java.util.concurrent.atomic.AtomicStampedReference;

 public class AtomicStampedReferenceTest {
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
Thread main = new Thread(() -> {
System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());
int stamp = atomicStampedRef.getStamp();
//获取当前标识别
try {
Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1);
//此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);
},"主操作线程");
Thread other = new Thread(() -> {
Thread.yield(); // 确保thread-main 优先执行
atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
},"干扰线程");
main.start();
other.start();
}
}

四、总结

通过对CAS的理解和实验,我们更加深刻的理解了CAS的乐观锁,以及在java中的相应实现和对应ABA补丁的实现。

沉淀再出发:java中的CAS和ABA问题整理的更多相关文章

  1. 沉淀再出发:java中的equals()辨析

    沉淀再出发:java中的equals()辨析 一.前言 关于java中的equals,我们可能非常奇怪,在Object中定义了这个函数,其他的很多类中都重载了它,导致了我们对于辨析其中的内涵有了混淆, ...

  2. 沉淀再出发:java中注解的本质和使用

    沉淀再出发:java中注解的本质和使用 一.前言 以前XML是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,XML的内容也越来越复杂,维护成本变高.于是就有人提 ...

  3. 沉淀再出发:java中线程池解析

    沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...

  4. 沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识

    沉淀再出发:java中的HashMap.ConcurrentHashMap和Hashtable的认识 一.前言 很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下 ...

  5. 沉淀再出发:关于java中的AQS理解

    沉淀再出发:关于java中的AQS理解 一.前言 在java中有很多锁结构都继承自AQS(AbstractQueuedSynchronizer)这个抽象类如果我们仔细了解可以发现AQS的作用是非常大的 ...

  6. 沉淀再出发:如何在eclipse中查看java的核心代码

    沉淀再出发:如何在eclipse中查看java的核心代码 一.前言   很多时候我们在eclipse中按F3键打算查看某一个系统类的定义的时候,总是弹出找不到类这样的界面,这里我们把核心对应的代码加进 ...

  7. 沉淀再出发:java的文件读写

    沉淀再出发:java的文件读写 一.前言 对于java的文件读写是我们必须使用的一项基本技能,因此了解其中的原理,字节流和字符流的本质有着重要的意义. 二.java中的I/O操作 2.1.文件读写的本 ...

  8. 沉淀再出发:再谈java的多线程机制

    沉淀再出发:再谈java的多线程机制 一.前言 自从我们学习了操作系统之后,对于其中的线程和进程就有了非常深刻的理解,但是,我们可能在C,C++语言之中尝试过这些机制,并且做过相应的实验,但是对于ja ...

  9. 沉淀再出发:在python3中导入自定义的包

    沉淀再出发:在python3中导入自定义的包 一.前言 在python中如果要使用自己的定义的包,还是有一些需要注意的事项的,这里简单记录一下. 二.在python3中导入自定义的包 2.1.什么是模 ...

随机推荐

  1. PHP之string之str_word_count()函数使用

    str_word_count (PHP 4 >= 4.3.0, PHP 5, PHP 7) str_word_count - Return information about words use ...

  2. Flume初始

    一.Flume是什么 Flume是一个数据,日志收集的一个组件,可以用于对程序,nginx等日志的收集,而且非常简单,省时的做完收集的工作.Flume是一个分布式.可靠.和高可用的海量日志采集聚合和传 ...

  3. Rechnernetz

    1.Der Aufbau des Internets 1.1 Randabschnitt Er besteht aus Rechner,der mit Internet verbunden ist.D ...

  4. json跨域问题

    一.跨域问题的原因: 1 浏览器的检查 2 跨域 3 XMLHttpRequest请求 二.跨域问题的解决: 1 禁止浏览器检查: 使用dos命令,在启动浏览器的时候,加一个参数: chrome -- ...

  5. 学会了ES6,就不会写出那样的代码

    用let不用var ES6之前我们用var声明一个变量,但是它有很多弊病: 因为没有块级作用域,很容易声明全局变量 变量提升 可以重复声明 还记得这道面试题吗? var a = []; for (va ...

  6. MySQL中使用SHOW PROFILE命令分析性能的用法整理(配合explain效果更好,可以作为优化周期性检查)

    这篇文章主要介绍了MySQL中使用show profile命令分析性能的用法整理,show profiles是数据库性能优化的常用命令,需要的朋友可以参考下   show profile是由Jerem ...

  7. php中的namespace 命名空间

    名字解释: namespace(命名空间),命名空间是从php5.3开始支持的功能.作用主要有两个:1.可以避免类名取得过长.2.当在多个框架配合使用时,同名的类之间不会冲突. 命名空间,看名字就知道 ...

  8. EF 查询数据不读取缓存的解决办法

    EF查询(不使用缓存):Set<T>().AsNoTracking() 今天工作中发现一个很妖的问题,修改产品界面,修改数据后,数据库的值发生变化,感觉掉坑里了. 然后发现读取对象的方法是 ...

  9. sdfsdfsdf

    i- i- i-1i- i- i- i- i- i-

  10. C# 程序执行时间差

    有时需要知道执行一个方法需要多少时间,这时会用到一个时间差TimeSpan DateTime startTime = DateTime.Now;//方法开始时间 //{ // 你需要测试的代码. // ...