Java的CAS刨析
一、什么是CAS
CAS的全称叫做Compare And Swap,即比较并交换,是一种原子操作,同时CAS是一种乐观机制。在java.util.concurrent包中的很多功能都是建立在CAS之上。如 ReenterLock 内部的 AQS,各种原子类,其底层都用 CAS来实现原子操作。
二、如何解决并发安全问题
在Java并发中,我们最初接触的应该就是synchronized关键字了,但是synchronized属于重量级锁,很多时候会引起性能问题,volatile也是个不错的选择,但是volatile不能保证原子性,只能在某些场合下使用。
像synchronized这种独占锁属于悲观锁,它是在假设一定会发生冲突的,那么加锁恰好有用,除此之外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正好可以进行某项操作,如果要是发生冲突呢,那我就重试直到成功,乐观锁最常见的就是CAS。
最常见的就是i++的例子,如下面代码所示:
public class Test { public volatile int i; public synchronized void add() { i++; } }
这种方法在性能上可能会差一点,我们还可以使用AtomicInteger,就可以保证i
原子的++
了。
public class Test { public AtomicInteger i; public void add() { i.getAndIncrement(); } }
我们来看一下getAndIncrement()方法的内部是什么。
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
再深入到getAndAddInt
():
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; }
可以看到,CAS 的思想很简单:三个参数,一个当前内存值 V、旧的预期值 A、即将更新的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。代码中首先调用getIntVolatile取到内存中的值,再用当前值与内存中的值进行比较,如果相等则写入新的值,并返回true循环结束,反之继续取值知道当前值与预期值相等。其中compareAndSwapInt是同通过cmpxchgl CPU指令来完成的是个原子操作。
三、CAS常见问题
1、ABA问题
如线程 1 从内存位置 V 取出 A,这时候线程 2 也从内存位置 V 取出 A,此时线程 1 处于挂起状态,线程 2 将位置 V 的值改成 B,最后再改成 A,这时候线程 1 再执行,发现位置 V 的值没有变化,尽管线程 1 也更改成功了,但是不代表这个过程就是没有问题的。
2、自旋问题
从源码可以知道所说的自选无非就是操作结果失败后继续循环操作,这种操作也称之为自旋锁,是一种乐观锁机制,一般来说都会给一个限定的自选次数,防止进入死循环。自旋锁的优点是不需要休眠当前线程,因为自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是休眠当前线程是提高并发性能的关键点,这是因为减少了很多不必要的线程上下文切换开销。但是,如果 CAS 一直操作不成功,会造成长时间原地自旋,会给 CPU 带来非常大的执行开销。