CAS原理
深入理解 CAS 的核心原理,以及它在多线程场景下的工作机制、优缺点和实际应用,我会从基础概念到底层实现,用通俗易懂的方式帮你讲清楚。
CAS 核心原理
CAS 是 Compare-And-Swap(比较并交换) 的缩写,是一种无锁的原子操作机制,也是实现并发编程的核心思想之一。它的核心目标是:在不使用锁的情况下,保证多线程对共享变量操作的原子性。
1. CAS 的基本逻辑
CAS 操作涉及三个核心参数:
V:要修改的共享变量(内存地址)A:预期值(线程读取到的变量当前值)B:新值(线程想要把变量改成的值)
CAS 的执行流程可以概括为:
线程在更新变量V时,先对比V的当前值是否等于自己之前读取的预期值A:
- 如果相等,说明没有其他线程修改过这个变量,就把
V的值更新为新值B,返回操作成功;- 如果不相等,说明其他线程已经修改过这个变量,当前线程不做更新,返回操作失败(线程可选择重试、放弃或执行其他逻辑)。
这个对比 + 交换的过程是CPU 级别的原子指令(如 x86 架构的
cmpxchg 指令),由硬件保证原子性,无需加锁。2. 通俗举例(理解 CAS 逻辑)
假设两个线程(线程 1、线程 2)要对共享变量
j=0 执行加 1 操作,用 CAS 实现的过程:- 线程 1 读取
j的值为0(预期值 A=0),准备更新为1(新值 B=1); - 线程 1 执行 CAS:对比内存中
j的值是否等于0→ 是,将j改为1,操作成功; - 线程 2 也读取
j的值为0(预期值 A=0),准备更新为1(新值 B=1); - 线程 2 执行 CAS:对比内存中
j的值(此时已被线程 1 改为1)≠ 预期值0→ 操作失败; - 线程 2 可选择自旋重试:重新读取
j的值(现在是1),再执行 CAS 把j改为2。
3. Java 中的 CAS 实现(以 AtomicInteger 为例)
Java 中
java.util.concurrent.atomic 包下的原子类(如 AtomicInteger)就是基于 CAS 实现的,核心依赖 Unsafe 类(提供底层内存操作)。核心代码示例(简化版):
java
public class AtomicInteger extends Number implements java.io.Serializable {
// 共享变量(volatile 保证可见性)
private volatile int value;
// Unsafe 类(JDK 内部类,提供底层 CAS 操作)
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; // value 变量的内存地址偏移量
static {
try {
// 获取 value 变量在内存中的偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 原子加1操作(核心 CAS 逻辑)
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// Unsafe 的 getAndAddInt 方法底层逻辑(简化)
// var1:当前对象(AtomicInteger 实例)
// var2:value 变量的内存偏移量
// var4:要增加的值(如1)
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 1. 读取当前内存中的值(预期值 A)
var5 = this.getIntVolatile(var1, var2);
// 2. 执行 CAS:对比内存值是否等于 var5,是则更新为 var5+var4
// 循环直到 CAS 成功(自旋)
} while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
}
关键说明:
value用volatile修饰,保证多线程间的可见性(一个线程修改后,其他线程能立即看到最新值);compareAndSwapInt是 native 方法,底层调用 CPU 原子指令,保证对比 + 交换的原子性;- 失败时通过
do-while循环自旋重试,直到 CAS 成功。
4. CAS 的优缺点
优点
- 无锁开销:无需加锁 / 解锁,避免了线程上下文切换、锁等待的性能损耗;
- 原子性:由硬件指令保证,比锁更高效(尤其高并发低冲突场景);
- 非阻塞:线程不会被挂起,失败后可重试或放弃,响应性更好。
缺点
- ABA 问题:
核心问题:变量值从 A 改为 B,又改回 A,CAS 会认为变量未被修改,导致逻辑错误。解决方案:使用版本号 / 时间戳(如
AtomicStampedReference),把 “值 + 版本号” 作为对比依据。 - 自旋开销:
高并发高冲突场景下,线程会一直自旋重试,占用 CPU 资源(可限制重试次数或改用锁)。
- 只能保证单一变量的原子性:
CAS 只能操作单个变量,如果是多变量复合操作(如
j = a + b),无法保证原子性,仍需用锁。
5. CAS vs 锁(核心区别)
| 特性 | CAS(无锁) | synchronized/ReentrantLock(有锁) |
|---|---|---|
| 核心机制 | 硬件原子指令 + 自旋 | 操作系统内核态锁(阻塞 / 唤醒线程) |
| 性能 | 低冲突时极高,高冲突时下降 | 低冲突时略差,高冲突时更稳定 |
| 适用场景 | 单一变量原子操作 | 复合操作、多变量操作 |
| 问题 | ABA、自旋开销 | 死锁、上下文切换开销 |
总结
- CAS 是硬件级别的原子操作,通过 “比较预期值 - 交换新值” 实现无锁原子更新,核心参数是内存地址 V、预期值 A、新值 B;
- Java 中
AtomicInteger等原子类基于 CAS 实现,结合volatile保证可见性; - CAS 优点是无锁高效,缺点是存在 ABA 问题、自旋开销,且仅适用于单一变量操作;
- 实际开发中,单一变量优先用 CAS(Atomic 类),复合操作优先用锁。
*
备注:公众号清汤袭人能找到我,那是随笔的地方
备注:公众号清汤袭人能找到我,那是随笔的地方

浙公网安备 33010602011771号