CAS
本页是 CAS 基础入口,用于先建立“比较旧值后原子更新”的直觉。要继续理解 CPU 原子指令、JDK 7/8 Atomic/Unsafe、JMM 内存语义、引用身份、缓存行竞争、LongAdder 和线上排查,请按顺序阅读:
CAS的全称是:
Compare And Swap(比较相同再交换)。是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。
CAS的作用:
CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证。CAS可以保证共享变量赋值时的原子操作。
CAS操作依赖3个值:共享内存中的值V,旧的预估值X,要修改的新值B,如果旧的预估值X等于共享内存中的值V,就将新的值B保存到内存中。
CAS 执行流程
flowchart TD
A[读取共享变量当前值 V] --> B[准备期望值 X 和新值 B]
B --> C{V 是否等于 X}
C -- 是 --> D[把 V 更新为 B]
C -- 否 --> E[更新失败]
D --> F[返回成功]
E --> G[重新读取或放弃]
G --> ACAS 的核心是“比较”和“交换”这两个动作必须是一个不可分割的原子操作。Java 中很多并发类都基于 CAS 实现,例如 AtomicInteger、AtomicLong、AtomicReference、LongAdder。
Java 中的使用示例
AtomicInteger count = new AtomicInteger(0);
public void add() {
count.incrementAndGet();
}incrementAndGet() 内部不是简单的 count = count + 1,而是不断读取旧值、计算新值、尝试 CAS 更新。如果更新失败,说明期间有其他线程修改过,就重新尝试。
CAS 和 synchronized 的区别
| 对比项 | CAS | synchronized |
|---|---|---|
| 类型 | 乐观并发控制 | 悲观锁 |
| 阻塞 | 通常不阻塞线程 | 竞争失败可能阻塞 |
| 适合场景 | 冲突较少、操作很短 | 临界区较大、逻辑复杂 |
| 成本 | 失败时自旋重试消耗 CPU | 阻塞和唤醒有线程调度成本 |
| 可读性 | 复杂逻辑较难维护 | 语义更直观 |
CAS 的三个典型问题
1. ABA 问题
线程 1 读取到值 A,准备 CAS 更新。期间线程 2 把 A 改成 B,又改回 A。线程 1 再执行 CAS 时发现还是 A,于是认为没有变化,但实际上数据已经被修改过。
解决方式:
- 使用版本号,每次修改都递增版本。
- 使用
AtomicStampedReference同时比较值和版本。 - 对业务对象使用不可变对象或唯一流水号。
2. 自旋开销
如果竞争非常激烈,CAS 会不断失败并重试,线程虽然没有阻塞,但会持续消耗 CPU。
解决方式:
- 控制自旋次数。
- 降低共享变量竞争。
- 使用分段累加,例如
LongAdder。 - 冲突很高时改用锁。
3. 只能保证单个变量原子更新
普通 CAS 只能更新一个变量。多个变量之间存在一致性要求时,仅靠 CAS 会让代码很复杂。
解决方式:
- 封装成一个不可变对象,用
AtomicReference整体替换。 - 使用锁保护多个变量。
- 使用事务或数据库约束保证一致性。
选择建议
flowchart TD
A[需要线程安全更新] --> B{临界区是否很短}
B -- 否 --> C[优先 synchronized 或 Lock]
B -- 是 --> D{竞争是否激烈}
D -- 否 --> E[可以使用 Atomic + CAS]
D -- 是 --> F{是否是计数累加}
F -- 是 --> G[使用 LongAdder]
F -- 否 --> CCAS 很适合简单、高频、低冲突的原子更新;如果业务逻辑包含多步骤判断、多个字段一致性或复杂异常处理,锁通常更清晰。
