CAS原理
JDK5之前Java是靠synchronized关键字保证同步,这种机制存在以下问题:
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 一个线程持有锁会导致其他需要此锁的线程挂起
- 如果一个优先级高饿线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险
synchronized是一种独占锁,会导致其他所有需要该锁的线程挂起,等待持有锁的线程释放锁。独占锁属于悲观锁。另一种更有效的锁是乐观锁,乐观锁其实已经没有锁的概念,而是假设没有冲突的去完成某项操作,如果因为冲突失败就重试,知道成功为止。
乐观锁用到的机制就是CAS,Compare And Swap
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时将内存值修改为B。这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。CAS的原子性是由CPI硬件指令实现保证的,即调用native方法调用由C++编写的硬件级别指令,jdk中提供Unsafe类执行这些操作。
下面代码利用CAS实现AtomicInteger
class AtomicInteger { private static final Unsafe unsafe = Unsafe.getUnsafe(); //在没有锁的机制下需要volatile修饰,保证线程间数据是可见的。 public volatile int value; public final int get() { return value; } //自增操作 public final int incrementAndGet() { for(; ;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } } //利用JNI来完成CPU指令的操作 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } }
乐观锁有下面的缺点:
- 只能保证一个共享变量的原子操作
- 长时间自旋可能导致开销大
- ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。