什么是CAS?
CAS:Compare And Swap,即比较再交换。是 JDK 提供的非阻塞原子性操作,他通过硬件保证了比较——更新操作的原子性。JDK 里面的 Unsafe 类提供了一系列的 compareAndSwap* 方法。
CAS 算法理解
- 与锁相比,使用比较交换会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。
简单来说就是,无锁的好处:在高并发的情况下,它比有锁拥有更好的性能。天生就是死锁免疫的。 - CAS 操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会呗挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
- 简单地说,CAS 需要你额外给出一个期望值,也就是你认为这个变量线程应该是什么样子的。如果变量不是你想象得那样,那说明他已经被别人修改过了。你就重新读取,再次尝试修改就好了。
- 在硬件层面,大部分现代处理器都已经支持原子化的 CAS 指令。在 JDK5.0 以后,虚拟机便可以使用这个指令来实现并发操作和并发数据结构,并且,这种操作在虚拟机中可以说是无处不在。
算法过程:
以 boolean compareAndSwapLong(Object obj, long valueOffset, long except, long update)方法为例:
其中 compareAndSwap 的意思是比较并交换。CAS 有四个操作数:对象内存的位置、对象中的变量的偏移值、变量预期值和新的值。如果对象 obj中内存偏移量为 valueOffset 的变量值为 except,则使用新的值update 替换旧的值 expect。这是处理器提供的一个原子性的命令。
简单的说,valueOffset 表示要更新的变量,except 表示预期值,update 表示新值。只有当 valueOffset=except 时,才会将 valueOffset 的值设为 update,如果 valueOffset!=except,则说明已经有其他线程做了更新操作,则当前线程什么都不做。
CAS 操作的 ABA 问题
假如线程 1 使用 CAS 修改初始值为 A 的变量 X,那么线程 1 会首先去获取当前变量 X 的值(为 A ),然后使用 CAS 操作尝试修改 X 的值为 B,如果使用 CAS 操作成功了,那么程序运行一定是正确的吗?其实未必,这是因为有可能线程 1 获取变量 X 的值为 A 后,在执行 CAS 之前,线程 2 使用 CAS 修改了变量 X 的值为 B,然后又使用 CAS 修改了变量 X 的值为 A。所以虽然线程 1 执行 CAS 时 X 的值是 A,但是这个 A 已经不是线程 1 获取时的 A 了。这就是 ABA 问题。
ABA 问题的产生时因为变量的状态值产生了环形转换,就是变量的值可以从 A 到 B,然后再从 B 到 A。如果变量的值只能朝着一个方向转换,比如 A 到 B,B 到 C,不构成环形,就不会存在问题。JDK 中的 AtomicStampedReference 类给每个变量的状态值都配备了一个时间戳,从而避免了 ABA 问题的产生。