CAS(Compare-And-Swap),它是一条CPU并发原语,用于判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS基本原理 |
unsafe类分析
如下是Unsafe类中的compareAndSwapInt,是一个本地方法,在unsafe.cpp中
CAS具体应用 |
O:预期值(旧值)
N:新值,表示我们准备更新V的值
当执行CAS后,如果V == O ,即旧值与内存中实际值相等,表示上次修改后没有任何线程再次修改此值,因此可以将N替换到内存中。
如果 V != O,表示该内存中的值已经被其他线程做了修改,所以无法将N替换,返回最新的值V
当多个线程使用CAS操作一个变量时,只有一个线程会成功,并成功更新变量值,其他线程均会失败。失败线程会重新尝试或将线程挂起(阻塞)
CAS实现原子操作三大问题 |
CAS的ABA问题
CAS算法实现的一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化,比如线程M从内存位置W取出值A,这时N也取出A,N操作后A变成了B,然后N将B又变回A,这时线程M在CAS操作时发现内存中仍然是A,然后M执行成功,M虽然执行成功,但实际上就出现了ABA问题。
解决方案:Java1.5开始,JDK的Atomic包里提供了一个类AtomicStampedRefernce来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查标志stamped是否为预期标志,如果全部一致,则继续。
CAS的循环时间长开销大问题
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销,如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升
pause指令的两个作用:
第一,它可以延迟流水线执行指令,使CPU不会消耗过多的执行资源
第二,它可以避免在退出循环的时候因内存顺序冲突而引起CPU流水线被清空
CAS的只能保证一个共享变量的原子操作问题
当对一个共享变量进行原子操作,循环CAS可以解决,但是如果是多个共享变量呢?循环CAS无法解决这个问题。
取巧的话就是合并共享变量:i=2,j=a -> ij=2a; 如此合并就可以操作了
解决方案:Java 5 开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象进行CAS操作