CAS,在Java并发应用中通常指CompareAndSwap,即比较并交换。
CAS是一个原子操作,它比较一个内存位置的值并且只有相等时修改这个内存位置的值为新的值,保证了新的值总是基于最新的信息计算的,如果有其他线程在这期间修改了这个值则CAS失败。
synchronized属于重量级锁,很多时候会引起性能问题。
volatile是轻量级锁,但是volatile不能保证原子性。
像synchronized这种独占锁属于悲观锁,它是在假设一定会发生冲突的,那么加锁恰好有用。
除此之外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正好可以进行某项操作,如果要是发生冲突呢,那我就重试直到成功。
乐观锁最常见的就是CAS。 我们在读Concurrent包下的类的源码时,发现无论是ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了CAS。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。
1 public class Test { 2 3 public AtomicInteger i; 4 5 public void add() { 6 i.getAndIncrement(); 7 } 8 } 9 10 public final int getAndIncrement() { 11 return unsafe.getAndAddInt(this, valueOffset, 1); 12 } 13 14 public final int getAndAddInt(Object var1, long var2, int var4) { 15 int var5; 16 do { 17 var5 = this.getIntVolatile(var1, var2); 18 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 19 20 return var5; 21 }
var5 = this.getIntVolatile(var1, var2);
var2是在内存中的偏移地址,通过这个地址拿到对象var1在内存中的值,赋给var5
然后比较var5是否跟对象var1在内存中的值相等,如果相等,则将这个值更新,如果不相等,则重试。
CAS是在JNI
里是借助于一个CPU
指令完成的,所以还是原子操作。
CAS的缺点:
ABA问题:
CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A
就会变成1A-2B-3A
。
目前在JDK的atomic包里提供了一个类AtomicStampedReference
来解决ABA问题。
这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。