CAS
CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。
CAS每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 是一个乐观锁。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
CAS 实现
对java.util.concurrent.atomic 包下的原子类 AtomicInteger 中的 compareAndSet 方法进行分析,就能发现最终调用的是 sum.misc.Unsafe 这个类。
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
Unsafe是一个不安全的类,unsafe 的cas 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg。Atomic::cmpxchg 的实现使用了汇编的 cas 操作,并使用 cpu 硬件提供的 lock信号保证其原子性。
ABA 问题
CAS 由三个步骤组成,分别是读取->比较->写回。
考虑这样一种情况,线程1和线程2同时执行 CAS 逻辑
- 线程1执行读取操作,获取原值 A,然后线程被切换走
- 线程2执行完成 CAS 操作将原值由 A 修改为 B
- 线程2再次执行 CAS 操作,并将原值由 B 修改为 A
- 线程1恢复运行,将比较值(compareValue)与原值(oldValue)进行比较,发现两个值相等。然后用新值(newValue)写入内存中,完成 CAS 操作
线程1并不知道原值已经被修改过了,在它看来并没什么变化,所以它会继续往下执行流程。对于 ABA 问题,通常的处理措施是对每一次 CAS 操作设置版本号。
java.util.concurrent.atomic 包下提供了一个可处理 ABA 问题的原子类 AtomicStampedReference。其compareAndSet方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用的该标志的值设置为给定的更新值。
CAS的缺点
CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。