CAS
1. CAS 是什么
CAS = Compare And Swap,也叫 Compare And Set。
它的核心意思很简单:
我先比较一下内存里的值是不是我预期的旧值,如果是,我就把它改成新值;如果不是,我就不改,说明有人比我先动过了。
你可以把它记成 3 个值:
V:内存中的当前值
A:我预期的旧值
B:我想改成的新值
逻辑就是:
1 | if (V == A) { |
更新失败
}
但这个 比较 + 修改 不是普通 if,那样会有线程安全问题。
CAS 的关键在于:这个操作是原子的,底层由 CPU 指令级别保证。
2. 它为什么重要
因为它提供了一种 不用加锁也能做并发更新 的思路。
传统加锁像这样:
1 | synchronized(lock) { |
CAS 的思路像这样:
1 | while (true) { |
也就是:
先乐观地认为没人改
试着更新
失败了就重试
所以 CAS 常被归到 乐观锁 思想里。
3. CAS 的完整流程
拿 AtomicInteger 自增举例最直观:
假设初始值是 5
线程 A 和线程 B 同时来做 +1
线程 A
读到旧值
5计算新值
6CAS 比较:当前内存值是不是
5是,更新成
6成功
线程 B
它一开始也读到旧值
5也想改成
6但它 CAS 时会发现,当前内存值已经不是
5了,而是6所以失败
失败后重新读取最新值
6再算新值
7再次 CAS
成功
所以 CAS 的精髓就是:
谁先改成功谁赢,失败的人不阻塞,自己重试。
4. CAS 为什么能保证线程安全
重点在这句:
比较和交换是一个原子操作。
不是 Java 代码本身 magically 安全,
而是 JVM 最终会借助底层能力去完成原子更新。
在 Java 并发场景里,JDK 1.8 的 ConcurrentHashMap 已经采用了 Node + CAS + synchronized 来保证线程安全,说明 CAS 是现代并发容器的重要基础之一。
你可以这样理解:
CAS 负责轻量级抢位
synchronized 负责复杂冲突处理
两者经常组合使用,不是非黑即白
5. CAS 和 synchronized 的区别
synchronized
悲观锁思路
先加锁,再操作
冲突大时更稳
线程可能阻塞、挂起、唤醒
涉及上下文切换,成本更高
CAS
乐观锁思路
先尝试修改,不加锁
失败就自旋重试
不会让线程挂起
低竞争下性能通常很好
一句面试话术你可以直接背:
CAS 是一种基于硬件原子指令实现的无锁并发机制,属于乐观锁思想。它通过比较内存值与预期值是否一致来决定是否更新,失败后通常采用自旋重试。
6. CAS 的典型应用
最常见的 3 个:
1. AtomicInteger / AtomicLong / AtomicReference
这些原子类几乎就是 CAS 的代言人。
1 | AtomicInteger count = new AtomicInteger(0); |
你表面看是自增,底层本质就是 CAS 重试。
2. ConcurrentHashMap
JDK 1.8 的 ConcurrentHashMap 放弃了 JDK 1.7 的 Segment 分段锁设计,改成 Node + CAS + synchronized,锁粒度更细。
3. AQS
像 ReentrantLock、CountDownLatch、Semaphore 这些很多同步器,底层状态变量 state 的更新都 heavily 依赖 CAS。
这个你后面学 AQS 时会突然觉得:
哦,原来 CAS 是整个 JUC 的地基。
7. CAS 的优点
1. 不需要阻塞线程
线程失败了不会进入阻塞态,而是继续尝试。
2. 减少上下文切换
比重量级锁轻。
3. 低竞争场景性能很好
线程不多,冲突不大时,CAS 非常香。
8. CAS 的缺点
这块是面试爱追问的重点。
1. 自旋会消耗 CPU
CAS 失败不会睡眠,而是一直重试。
如果竞争特别激烈:
线程 A 改
线程 B 失败重试
线程 C 也失败重试
大家一直空转
就会疯狂烧 CPU。
所以 CAS 适合:
临界区小
冲突低
操作快
不适合特别高竞争的大量写场景。
2. ABA 问题
这个是 CAS 最经典的坑。
你以为值没变,其实它变过。
比如:
初始值 A
线程 1 读取到 A,准备改
线程 2 把 A 改成 B
线程 2 又把 B 改回 A
线程 1 再去 CAS,发现还是 A,于是认为“没被改过”
实际上它已经被动过了
这就是 ABA 问题。
怎么解决
加版本号。
Java 里常见做法:
AtomicStampedReferenceAtomicMarkableReference
比如把值从:
- A
变成:
A + 版本1
B + 版本2
A + 版本3
这样就能知道,虽然值看起来又回到了 A,但版本已经不是原来的了。
3. 只能保证单个共享变量的原子性
CAS 天然适合更新一个值。
比如:
一个
int一个引用
但如果你要同时改两个值:
1 | x = 10; |
那单独一个 CAS 搞不定“整体一致性”。
解决思路一般是:
加锁
把多个字段封装成一个对象,用
AtomicReference整体替换
9. volatile 和 CAS 的关系
这个点非常容易混。
volatile 做什么
保证可见性
禁止指令重排
不保证复合操作原子性
CAS 做什么
- 保证某次比较交换的原子性
所以:
volatile 不是 CAS,CAS 也不是 volatile。
但它们经常配合使用。
比如你可以这样理解:
volatile 保证你读到的是新值
CAS 保证你改值时是原子的
10. 一个很经典的面试例子
为什么 AtomicInteger 线程安全,而 volatile int 不够?
volatile int count
count++;
这不是原子操作,它会拆成:
读 count
加 1
写回 count
哪怕 count 是 volatile,多个线程还是可能写丢。
AtomicInteger
count.incrementAndGet();
底层用 CAS 保证原子更新,所以线程安全。
11. 面试版总结
你可以这样答,比较完整:
CAS 是 Compare And Swap,也就是比较并交换。它包含三个操作数,内存值、预期旧值和要更新的新值。
只有当内存值等于预期值时,才会把内存值更新成新值,否则更新失败。
CAS 是一种基于硬件原子指令实现的无锁并发方案,属于乐观锁思想。
它常用于 AtomicInteger、AQS、ConcurrentHashMap 等并发组件中。
优点是低竞争场景性能高,不会阻塞线程;缺点是会自旋消耗 CPU,存在 ABA 问题,并且只能保证单个共享变量的原子更新。
mindmap
root((CAS))
定义与本质
Compare And Swap
比较旧值
交换新值
原子操作
乐观锁思想
核心流程
读取当前值
比较预期值
一致则更新
不一致则失败
自旋重试
核心组成
V
内存旧值
A
预期值
B
新值
底层原理
CPU原子指令
Unsafe或VarHandle
volatile可见性配合
无锁并发
典型应用
AtomicInteger
AtomicLong
AtomicReference
AQS
ConcurrentHashMap
优点
轻量
非阻塞
低竞争性能好
减少线程切换
缺点
自旋耗CPU
ABA问题
只能保证单变量原子性
高竞争下性能波动
ABA问题
A变B再变A
CAS误判未变化
解决
AtomicStampedReference
版本号
对比
synchronized
悲观锁
线程阻塞
volatile
保证可见性
不保证复合原子性
面试高频
CAS是什么
CAS和synchronized区别
CAS为什么是乐观锁
ABA怎么解决
AtomicInteger为什么线程安全