对CAS的理解和分析
CAS
CAS(CompareAndSet 或 CompareAndSwap)又称 无锁,乐观锁,实现方式是非阻塞同步。
CAS指令有三个操作数,分别是内存位置(在Java中可以简单地理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和准备设置的新值(用B表示)。
CAS指令执行时,当且仅当V符合A时,处理器才会用B更新V的值,否则它就不执行更新。但是,不管是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作,执行期间不会被其他线程中断。CAS指令的底层是原子操作,保证了原子性,解决了指令交错问题。
例如 java.util.concurrent.atomic.AtomicInteger#compareAndSet 方法
/** * @param expect the expected value * @param update the new value * 如果调用该方法的值是期望值(expect),那么更新 value = update,返回true */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
在JDK 5之后,Java类库中才开始使用CAS操作,该操作由sun.misc.Unsafe
类里面的compareAndSwapInt()
和compareAndSwapLong()
等几个方法包装提供。HotSpot虚拟机在内部对这些方法做了特殊处理,即时编译出来的结果就是一条平台相关的处理器CAS指令,没有方法调用的过程,或者可以认为是无条件内联进去了。
AtomicInteger等底层的 value 被 volatile 修饰,保证了可见性和禁止指令重排,可见性保证了多个线程间 CAS 操作,可以访问到最新的 value。
CAS和synchronized的对比
CAS 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
- CAS 是基于乐观锁的思想:其他线程可以修改共享变量,不加任何锁。
- synchronized 是基于悲观锁的思想:给共享变量上锁,阻塞其他想获得锁的线程,直到锁的释放。
CAS 体现的是无锁并发、无阻塞并发
- 因为没有使用 synchronized ,所以线程不会陷入阻塞,这是效率提升的因素之一
- 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。
ABA问题及解决
CAS操作存在一个逻辑漏洞:如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然为A值,那就能说明它的值没有被其他线程改变过吗?这是不能的。因为如果在这段期间它的值曾经被改成B,后来又改为A,那CAS操作就会误认为它从来没有改变过。
change A -> B true
change B -> A true
change A -> C true
解决方法:
控制变量值的版本来保证 CAS 的正确性。使用带有标记的原子引用类 AtomicStampedReference,为变量值提供版本号。
但是大部分情况下ABA问题不会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更为高效。