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