CAS 是英文单词 Compare And Swap 的缩写,翻译过来就是比较并替换。
CAS机制中使用了3个基础操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量时,只有当预期值与内存地址V中对应的值相同时,才能修改内存地址V 中的值为B。
举个栗子:
1.在内存地址V 中存储了一个值为10的变量
2.线程A想要把变量的值+1,对于线程A来说,旧的预期值是A=10,要修改的新值为 B= 11;
3.但在线程A执行之间,线程B抢先一步,将内存地址V中的值修改为11;
4.这时候线程A执行时,发现内存地址V中的值为11,和自己拿的预期值10不相等,提交就失败了;
5.线程A重新获取当前内存地址中的值,并重新计算想要修改的值,此时对于线程A来说,A=11,B=12。这个重新获取值的过程就是自旋。
从上面的例子中可以看出,CAS是乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。而synchronized属于悲观锁,认为并发问题严重,不加锁不行。
CAS 方法解读以 Unsafe#compareAndSetInt 方法为例进行说明
@HotSpotIntrinsicCandidate
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
- o 是操作的对象
- offset 是 o 对象中某字段在内存中的偏移量(比如对象 AtomicInteger 中有一个 volatile int value 的字段,可以理解为是内存地址V)
- 读取传入对象 o 在内存中偏移量为 offset 位置的值与期望值 expected 作比较。
- 相等就把 x 值赋值给 offset 位置的值。方法返回 true。
- 不相等,就取消赋值,方法返回 false。
我们看一下AtomicInteger当中常用的自增方法incrementAndGet:
调用了Unsafe类的方法,并定义了一个volatile修饰的值表示当前内存中的值,用volatile修饰能保证在线程中的可见性。
什么是unsafe呢?
unsafe为我们提供了硬件级别的原子操作。
public class AtomicInteger extends Number implements java.io.Serializable {
// Unsafe 类定义
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取变量 value 在内存中对应的地址偏移量
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset // 内存中对应的地址偏移量获取
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//保证数据的可见性
private volatile int value;
// expectedValue 预期值、newValue 修改值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
CAS的缺点
- cpu开销大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。 - 不能保证代码块的原子性
CAS机制保证的是一个变量的原子性,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。 - ABA的问题
假设在内存中有一个值为A的变量,存储在内存地址V 中。
此时由三个线程想使用CAS机制修改这个值,线程A和线程B已经获取到当前值A,线程C还没有获取到,线程A抢先一步修改了内存地址V中的值B,而线程B由于种种原因阻塞了,这时线程C执行,把内存地址V中的值改为A,好巧不巧,线程B恢复了运行状态,拿着阻塞前的A值去内存地址V中比较,发现相同,就把值改为了B。这就是ABA的问题。
加个版本号就行了!
真正要做到严谨的CAS机制,我们在compare阶段,不能只比较期望值A和内存地址中的实际值,还要比较变量的版本号。
上面的例子中:
假设地址V中存储着变量值A,当前版本号是01。线程1获取了当前值A和版本号01,想要更新为B,但是被阻塞了。这时候,内存地址V中变量发生了多次改变,版本号提升为03,但是变量值仍然是A。
随后线程1恢复运行,进行compare操作。经过比较,线程1所获得的值和地址的实际值都是A,但是版本号不相等,所以这一次更新失败。
- java语言CAS底层如何实现?
利用unsafe提供的原子性操作方法。
- 什么事ABA问题?怎么解决?
当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。
利用版本号比较可以有效解决ABA问题。



