深入理解 CAS 及 ABA问题
CAS,Compare And Swap,即比较并交换。
CAS是Atomic 包的核心,更是整个J.U.C 包的 基石。
本文 将通过AtomicInteger这个类,来分析是如何通过CAS来保证 Atomic的原子性的。
直接进入 AtomicInteger 这个类,可以看到
AtomicInteger 中定义了一个变量value并且用 valatile来修饰的, 还有个静态代码块 的 valueOffset变量可以得到 这个value的值。
所以我们在初始化 AtomicInteger 这个类,并设值后,调用 AtomicInteger 的其它方法,就是用的这个 valueOffset来接收了
然后进入 AtomicInteger 的 incrementAndGet 方法,该方法是 默认 +1 操作。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
this 当前类,没什么好说的,
valueOffset ,就是此时 AtomicInteger 类保存的 value 值。
进入 该方法的实现
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2); //从主存中 去拿这个变量最新的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
分析如下:如果当前value 的值是 5,那么进行 5 + 1 这个操作
var1 是当前的AtomicInteger 这个类
var2 是value 当前的值 5 ,var 4是准备要加 1
var4 是当前准备从内存去取得最新的 AtomicInteger 的 value值,
然后进入 compareAndSwapInt 方法 ,这就是我们说的 CAS了
var2是 预期的值 ,var5 是主存中的值, var5+var4是要更新的结果,
当且只当 var2与v5相等时,才会返回更新的结果值。
因为v2的值是当前线程中工作内存的值,只有确认自己读到的值跟主存中的值一致的时候,才能去修改这个值。
在多线程竞争的情况下,如果var2主存中的值 被其它线程修改成3了,
那var2 等于2 的时候 就不等于 主存中的3 ,那么就不会进行相加。
但是使用Atomic这个类 会有一个ABA的问题。
CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的ABA问题。对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。
如何解决呢?
Java提供了AtomicStampedReference这个类来解决。
AtomicStampedReference通过版本号stamp,从而避免ABA问题。
compareAndSet有四个参数,分别表示:预期引用、更新后的引用、预期标志、更新后的标志