CAS(Compare and Swap)
CAS 全称是 compare and swap(比较并且交换),是一种用于在多线程环境下实现同步功能的机制,其也是无锁优化,或者叫自旋,还有自适应自旋(说法不算准确,底层还是存在锁,后面会讲)。
由java到汇编来看CAS底层实现
//以AtomicInteger类的compareAndSet方法举例。
//其源码:
//expect与旧值一致,则用update替换旧值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
问题:CAS是原子性的吗?从上面的compareAndSet方法为例,需要先与旧值比较,然后再替换旧值,那是不是代表是两个操作呢?
下面从源码开始分析:
1)compareAndSet源码调用unsafe.compareAndSwapInt:
//unsafe这个类大部分方法都是native,调用底层C的代码。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
2)compareAndSwapInt在C++层面的源码:
其核心指令:Atomic::cmpxchg(x,addr,e),cmpxchg是compare和exchange缩写。
3)汇编实现:Atomic::cmpxchg(x,addr,e)
在汇编层面,在多线程中,比较与交换操作加了一把lock锁,锁住了比较与交换这两个操作,所以在这个过程中是原子性的。
4)多看一个指令,获取lock指令的方法:Lock_IF_MP
结论
因为CAS底层调用的汇编命令Atomic::cmpxchg,在多线程情况下,会加锁,所以确保了比较并交换操作的原子性。
ABA问题
-
简单来说,ABA问题就是某个线程在其他线程还未进行CAS操作时,修改了变量A值=B,然后又修改B=A,然后其他线程开始CAS操作,比较原先的值=A,所以正常执行。但其他线程不知道此时的A已经被转换成了B,又换回了A,相当于不知道A是个二手的,还以为是新货。
-
代码解释:
//以下假设有两个线程
Integer oldValue = 1;//线程1要修改oldValue=2
//此时线程2进来,将oldValue改值:1->0->1,
oldValue = 0;
oldValue = 1;
atomicInteger.compareAndSet(1,2);//线程1执行
//线程1不知道线程2改过这个值,因为这一条代码直接判断oldValue=1,
//是正确的,所以正常的执行oldValue=2.
如何解决?添加版本号:
-
为要修改的资源添加版本号,每修改一次,版本号+1,然后CAS操作时,校验这个版本号,版本号相同,才能进行CAS,否则失败。
-
为要修改的资源添加版本号,每修改一次,版本号+1,然后CAS操作时,校验这个版本号,版本号相同,才能进行CAS,否则失败。
-
例如上面的例子,为oldValue添加一个版本号version=1,当线程2修改1:(version=1)->0:(version=2)->1:(version=3)后,此时版本号应该version=3,而线程1执行CAS,就比较此时是否version=1,如果不等,那么不允许进行CAS操作。