什么是CAS
CAS全称Compare And Swap,即比较并交换。CAS的功能是,先判断内存中的目标变量与给定期望值是否相同,如果相同,则为目标变量赋一个新值。CAS是乐观锁的一种实现,在锁竞争不大的情况下使用CAS实现同步的效率比悲观锁(synchronized等3)同步实现高,在Java并发包中具有广泛应用。
Java中CAS的并发原语全部体现在sum.misc.Unsafe类中,Unsafe类中的方法均为native方法,在相对应的C语言的函数实现中,最终在C函数中调用汇编指令实现CAS操作。总结来说就是CAS实现是基于汇编指令的,也就意味着CAS是一种依赖于硬件的操作,他的原子性与线程安全性完全由硬件保障(Cpu原子指令)。
ABA问题
所谓ABA问题就是,当有多个线程对同一个原子类进行操作时,在某一线程内原子类的值先由A被修改成B,再由B修改回A ,此时这两个修改操作其他线程是无法察觉的。
简单示例说明如下:
public class Test {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(1);
atomicInt.compareAndSet(1, 2);
atomicInt.compareAndSet(2, 1);
System.out.println(atomicInt.get());
boolean result = atomicInt.compareAndSet(1, 2);
System.out.println(result);
}
}
输出结果如下:
1
true
解决方案
ABA问题出现的根本原因是CAS只关注值的最终结果,而不关注值修改的过程,只需要对修改过程也关注并记录就可以解决问题,Java中实际上是添加了版本号来解决ABA问题,具体体现在Java并发包下的原子类AtomicStampedReference。
AtomicStampedReference内用静态内部类Pair的示例存储数据,静态内部类Pair定义了两个字段,一个用于存放引用,另一个则是版本号,结构如下:
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;
示例如下:
public class Test {
public static void main(String[] args) {
AtomicInteger v1 = new AtomicInteger(1);
AtomicInteger v2 = new AtomicInteger(2);
AtomicStampedReference<AtomicInteger> reference = new AtomicStampedReference<AtomicInteger>(v1, 0);
// 记录原始版本
int stamp = reference.getStamp();
// 模拟其它线程ABA修改变量
reference.compareAndSet(v1, v2, reference.getStamp(),
reference.getStamp() + 1);
reference.compareAndSet(v2, v1, reference.getStamp(),
reference.getStamp() + 1);
System.out.println(reference.getReference());
boolean result = reference.compareAndSet(v1, v2, stamp, stamp + 1);
System.out.println(result);
}
}
输出结果如下
1
false
CAS的其他缺点
- 只能保证一个变量的原子性,无法保证整个代码块的原子性。
- 自旋带来的Cpu开销大,在高竞争环境下使用CAS修改某变量,修改失败时只能进入死循环自旋继续尝试修改,浪费Cpu资源。