CAS:CompareAndSwap / CompareAndSet 比较和交换

首先聊一聊CAS是个什么概念:CAS字面上理解就是比较和交换。CAS包含三个元素。
期望的旧数据:以上图为例,读取m的值 m=4 ,“4”就是期望的旧数据。
最新的数据:执行m++ 后,m=5 ,"5"就是最新的数据。
内存地址:访问的内存地址,即m存放的地址,后续需要跟改m交换。
为什么叫做期望的旧数据呢?下面说一下CAS的过程。
首先,我们只看上图的左半部分。此时内存中的m=4 ,程序读取m后做m++操作,此时m变成了5。在将m=5更新到内存之前,执行CAS操作,首先判断内存中的数据还是不是为4,程序期望内存中的数据依然为4,即:期望的旧数据(意思就说在我m ++ 操作的过程中,内存中的数据没有被修改过)。CAS比较和交换,此时比较期望的旧数据与内存的数据是否相等,相等,交换内存中的数据,将内存中的数据设置为5,如果此时不相等,说明在我执行m++的过程中,内存中的数据被修改过,重新拉取内存中的数据,重复执行CAS自旋。这就是CAS的自旋操作。
CAS会产生一个ABA问题
我们看上图的右半部分,在我左边的程序执行m++的过程中,我右边的程序同样在操作内存中的m,首先将m改成3,然后将m改成5,最后再将m改成4,右边操作做完了之后,左边的程序此时CAS比较期望的旧数据与内存中的数据,m都是等于4。更新左边程序的结果m=5到内存中。
这就是ABA问题, m=4 看作A ,中间将m修改成其他的值这个就是B,最后再将m改为4 又变成了A。看上去m似乎还是4,但是其实他中间已经被修改过了。如果是基本类型修改前的m=4与修改后的m=4并没有影响。但是如果是引用数据类型,可能其他的变量被修改过了,就会产生问题,此时可以加一个version版本号区分ABA问题中的两个A,比如 第一个A版本为V.01 第二个A版本为V.02,CAS比较两个A相等,此时继续比较版本号,判断版本号不相等,说明内存中的数据被改动过,重新执行CAS自旋。
下面聊聊CAS实际场景
在JUC包下存在这样一种类型的类:AtomicXXX
AtomicInteger,AtomicLong,AtomicDouble 等。
它们内部都维护了CAS自旋锁。下面我们以AtomicInteger为例:
/**
* 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);
}
可以看到AtomicInteger中有上面两个方法getAndIncrement\getAndDecrement两个原子性的方法,分别是增加1和减少1。
下面我们以getAndIncrement为例,unsafe类调用getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
可以看到whilt条件中有一个compareAndSwapInt方法,这个就是CAS的自旋操作。
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
我们在跟进去,发现CAS是native修饰的方法,CAS自旋是CPU源于支持的方法。