Appearance
CAS 与无锁原子基础
一句话定义
CAS(Compare-And-Swap) 是 CPU 提供的原子原语:「只有当内存里的值仍是我看到的旧值时,才写成新值」;是 Atomic*、很多无锁结构的基石,属于乐观并发思路。
可运行对比实验:volatile-vs-atomic-vs-lock · VolatileVsAtomicVsLock.java
为什么要学
读 volatile、AtomicInteger、ConcurrentHashMap 之前,需要分清:
| 概念 | 解决什么 |
|---|---|
| volatile | 可见性 + 部分有序性;不保证 count++ |
| CAS / Atomic* | 单变量的原子读-改-写 |
| synchronized / Lock | 互斥 + 多步复合逻辑 |
CAS 在做什么(口语)
比喻:白板上的数字是 10。你和小王都要改成 11:
- 你先读:旧值 = 10
- 你算:新值 = 11
- 你去换:仅当白板上仍是 10 才写成 11;若已被别人改成 11,你失败重试
这就是 compareAndSet(expected, update) 的心智模型。
java
// AtomicInteger 内部语义(简化)
boolean compareAndSet(int expect, int update) {
// 硬件保证:读-比较-写 这一步原子
if (current == expect) { current = update; return true; }
return false;
}优劣
| 优势 | 劣势 |
|---|---|
| 无锁(通常不阻塞线程) | 高冲突时自旋重试,烧 CPU |
| 低冲突下延迟小 | 只适合单变量或能封装成原子状态的问题 |
| 表达「乐观更新」清晰 | ABA 等语义问题要额外处理 |
| 适合计数、状态位、引用切换 | 多字段一致性仍要锁或重新设计 |
乐观锁 vs 悲观锁(概念层)
| 乐观(CAS / Atomic*) | 悲观(synchronized / Lock) | |
|---|---|---|
| 心态 | 先假设不会冲突,冲突再重试 | 先占坑再干活 |
| Java 代表 | AtomicInteger、LongAdder | synchronized、ReentrantLock |
| 冲突低 | 通常更快 | 略重 |
| 冲突高 | 自旋可能更差 | 排队阻塞可能更稳 |
本文只建立概念;代码对比见 volatile lab 场景 2。
Java 里常见 API
| API | 用途 |
|---|---|
AtomicInteger / AtomicLong | 计数、序号 |
AtomicReference<V> | 引用整体切换(配置快照) |
AtomicStampedReference | 带版本号,缓解 ABA |
LongAdder | 极高并发计数,空间换冲突分散 |
VarHandle(JDK 9+) | 更底层的 CAS 入口 |
incrementAndGet() 等在底层就是 CAS 循环。
ABA 问题(知道即可)
线程 A 读到值 V1 → 被 B 改成 V2 又改回 V1 → A 的 CAS 仍成功,但中间状态已变。
引用类型若不能容忍「对象已被换掉又换回来」,用 AtomicStampedReference 或加版本号。
Android / 工程常见用途
| 场景 | 倾向 |
|---|---|
| 点赞数、下载计数 | AtomicInteger / LongAdder |
| 一次性开关、熔断 | AtomicBoolean |
| 配置引用整体替换 | AtomicReference + 不可变对象 |
| UI 状态多字段联动 | 不用 Atomic 硬凑,用不可变 state + StateFlow 或锁 |
ConcurrentHashMap 桶头 CAS | 集合底层;见 map lab |
和后续专题的关系
- volatile 适用/不适用 → volatile-vs-atomic-vs-lock
- 总览 → Java 并发模型总览 §3、§4
复习检查题
CAS 保证的是什么?不保证什么?
答:保证单次「读旧值 → 比较 → 条件成立才写入」在硬件上是原子的,适合单变量读-改-写(如
incrementAndGet)。不保证多字段一致性、长临界区业务逻辑,也不自动解决 ABA;高冲突时只保证正确性,不保证性能(会自旋烧 CPU)。为什么
volatile不能替代AtomicInteger做count++?答:
count++是读、加、写三步,volatile只保证每次读/写可见,不保证三步整体互斥。多线程仍会丢更新。AtomicInteger用 CAS 把「比较旧值再写入」合成一步原子语义。高并发计数何时考虑
LongAdder而不是AtomicLong?答:写冲突极高、热点集中在少数计数器时,
AtomicLong会大量 CAS 重试。LongAdder用分段 Cell 分散写压力,读时求和,以空间换吞吐。读多写少、需要强一致实时读值时仍可能用AtomicLong或锁。什么是 ABA?什么场景需要关心?
答:线程 A 读到值 V1,期间被改成 V2 又改回 V1,A 的 CAS 仍成功,但中间状态已变。纯数值计数通常无所谓;引用/链表节点等语义下,「对象已被替换又换回来」可能出错,需
AtomicStampedReference或版本号。
速记
- CAS = 比较旧值再更新,失败就重试。
- Atomic* = 乐观、单变量;锁 = 悲观、复合逻辑。
- 冲突低用 CAS 爽,冲突高要警惕自旋。