一文彻底掌握 CAS(Compare-And-Swap)
—— 并发编程绕不开的底层真相
本文是一篇“一次看懂、以后不再查资料”的 CAS 终极文章。
目标很明确:只要你把这篇文章完整看完,以后无论是面试、写并发代码、看源码、做系统设计,再遇到 CAS,都不需要再查任何资料。
先给结论(重要)
CAS 是一种由 CPU 硬件直接支持的原子操作,用“比较 + 交换”的方式,在不加锁的情况下完成并发安全更新,是现代高并发系统的底层基石。
你后面看到的:
- AtomicInteger
- ConcurrentHashMap
- AQS
- ReentrantLock
- LongAdder
- 无锁队列
- 高性能并发框架
底层都绕不开 CAS。
一、为什么会有 CAS?
1. 并发的根本问题是什么?
多个线程同时修改同一份共享数据。
比如:
线程 A:value = 1 → +1 → 写回
线程 B:value = 1 → +1 → 写回
结果本该是 2,却变成了 1。
2. 传统解决方案:加锁
synchronized (lock) {
value++;
}
锁的问题不是不能用,而是代价很高:
- 阻塞线程
- 上下文切换
- 内核态参与
- 高并发下吞吐下降明显
于是出现一个问题:
能不能不加锁,也保证并发安全?
答案就是:CAS。
二、CAS 到底是什么?
1. CAS 的完整定义
CAS = Compare-And-Swap(比较并交换)
它是一个原子操作,由 CPU 指令直接保证。
标准形式:
CAS(内存地址 V, 期望值 A, 新值 B)
执行规则只有一条:
- 如果
V == A→ 把V改成B - 如果
V != A→ 什么也不做
整个过程不可被中断。
2. CAS 的一句话模型
“如果现在的值还是我看到的那个值,我才更新;否则我放弃。”
三、CAS 是怎么实现并发安全的?
1. 核心思想:乐观并发控制
CAS 的思路不是“先锁住”,而是:
- 我假设没人改
- 我试着改
- 如果发现被别人抢先改了
- 那我再试一次
这就是乐观锁思想。
2. CAS 的典型使用方式:自旋
while (true) {
int old = value;
int next = old + 1;
if (CAS(value, old, next)) {
break;
}
}
这个过程叫:
CAS 自旋(Spin + Retry)
失败不是异常,是预期内行为。
四、CAS 为什么“无锁”但又是安全的?
1. 关键在“原子性”
CAS 的原子性不是 JVM 保证的,而是:
CPU 指令级别保证的
例如:
- x86:
CMPXCHG - ARM:
LDREX / STREX
在硬件层面:
- 比较
- 判断
- 写入
是一个不可分割的整体。
2. CAS vs 锁的本质区别
| 对比维度 | CAS | 锁 |
|---|---|---|
| 是否阻塞 | 否 | 是 |
| 是否切换线程 | 否 | 是 |
| 冲突处理方式 | 重试 | 等待 |
| 适合场景 | 低/中竞争 | 高竞争 |
| 性能 | 高 | 稳定但慢 |
五、CAS 的三大经典问题(面试必问)
1️⃣ ABA 问题(必须理解)
什么是 ABA?
变量变化过程:
A → B → A
CAS 只关心:
现在是不是 A?
它不知道中间发生过变化。
为什么这是问题?
在以下场景会出错:
- 链表
- 栈
- 对象引用
- 资源回收
解决方案
引入版本号(或时间戳)
(A, version=1) → (B, 2) → (A, 3)
Java 对应工具:
AtomicStampedReferenceAtomicMarkableReference
2️⃣ 自旋导致 CPU 空转
CAS 失败时会:
- 不阻塞
- 不休眠
- 一直重试
在高竞争下:
- CPU 使用率飙升
- 吞吐反而下降
结论:
CAS 不是银弹,高并发 ≠ 一定用 CAS。
3️⃣ 只能保证“一个变量”的原子性
CAS 天生只能操作:
- 一个内存位置
无法直接保证:
- 多变量一致性
- 复杂事务逻辑
这也是为什么:
CAS 通常是并发工具的“底层原语”,而不是直接业务工具。
六、CAS 在 Java 并发体系中的真实位置
1. Atomic 类族
AtomicIntegerAtomicLongAtomicBooleanAtomicReference
👉 最纯粹的 CAS 封装
2. AQS(核心中的核心)
ReentrantLockSemaphoreCountDownLatch
AQS 内部关键状态:
volatile int state;
所有状态变更:
全部靠 CAS
3. ConcurrentHashMap
- 桶初始化
- 节点插入
- 扩容协作
大量使用 CAS + synchronized 混合策略。
4. LongAdder
为了解决:
- AtomicLong 在高并发下 CAS 失败率高
方案是:
CAS + 分段 + 低冲突设计
七、什么时候该用 CAS?
适合用 CAS 的场景
- 计数器
- 状态标志
- 配置热更新
- 并发统计
- 无锁队列 / 栈
- 框架级并发组件
不适合直接用 CAS 的场景
- 高冲突写操作
- 多变量强一致
- 复杂业务事务
- 可读性要求高的业务代码
八、一句“工程级”总结(非常重要)
CAS 是并发世界的“原子操作基石”,不是业务代码的通用解决方案。
- 你一定要理解它
- 但不一定要自己手写它
- 大多数时候,用成熟并发工具类
九、终极记忆模型(背下来就够了)
CAS = 硬件原子指令 + 乐观并发思想 + 自旋重试机制
以及一句更狠的:
现代 Java 并发框架,本质上是在“如何更聪明地使用 CAS”。
十、如果你再看到 CAS,可以直接这样理解
- 看到 Atomic → CAS
- 看到 AQS → CAS
- 看到无锁 → CAS
- 看到高性能并发 → CAS
你不需要再查资料,只需要问自己一句:
这里的并发冲突,是低频可重试,还是高频需要阻塞?
这就是 CAS 的全部价值判断。