一、CAS是什么?
CAS是compare and swap的简称,从字面上理解就是比较并更新,这是一个实现并发算法时常用到的一种技术。简单来说:从某一个内存上取V,和预期值A进行比较,如果内存V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止。这是一种实现并发算法时常用到的技术,是乐观锁(与之相对synchronized是一种悲观锁)。
二、原理
CAS操作需要有三个操作数:内存地址V,旧的预期值A,即将要更新的目标值B
CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。
三、具体实现方式
Java中的Atomic系列就是使用CAS实现的,以下就以AtomicInteger为例来看下Java的实现过程:
3.1 属性
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); }
}
//变量用volatile修饰
private volatile int value;
3.2 自增
对当前的值自增1
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
这里调用了Unsafe的一个方法,实现如下:
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;
}
这里又调用了本类的compareAndSwapInt方法:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
这个方法利用CAS操作,将var4和内存偏移到的变量进行对比,如果相等,那么将这个地址所代表的变量设置为var5,否则返回false
四、CAS优点和缺点
4.1 缺点
4.1.1 ABA问题
假如线程1从内存X中取出A,这时候另一线程2也从X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程是有问题的。比如链表的头在变化了两次后恢复了原值,但是不代表链表没有发生变化。
所以JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。
4.1.2 循环时间开销很大
CAS通常是配合无限循环一起使用的,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
4.1.3 只能保证一个变量的原子操作
当对一个变量进行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个变量进行操作时,CAS目前无法直接保证操作的原子性。但是我们可以通过以下两种方式来解决:
- 使用互斥锁来保证原子性
- 将多个变量封装成对象,通过AtomicReference来保证原子性。
4.2 优点
和Synchronized相比,省去了挂起线程、恢复线程的开销,资源耗费少