Skip to content

CAS

本页是 CAS 基础入口,用于先建立“比较旧值后原子更新”的直觉。要继续理解 CPU 原子指令、JDK 7/8 Atomic/Unsafe、JMM 内存语义、引用身份、缓存行竞争、LongAdder 和线上排查,请按顺序阅读:

  1. CAS从CPU原子指令到Java原子类完整原理
  2. ABA问题从状态历史到版本戳完整原理
  3. volatile与Atomic全过程

CAS的全称是:

Compare And Swap(比较相同再交换)。是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。

CAS的作用:

CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证。CAS可以保证共享变量赋值时的原子操作。

CAS操作依赖3个值:共享内存中的值V,旧的预估值X,要修改的新值B,如果旧的预估值X等于共享内存中的值V,就将新的值B保存到内存中。

CAS 执行流程

mermaid
flowchart TD
    A[读取共享变量当前值 V] --> B[准备期望值 X 和新值 B]
    B --> C{V 是否等于 X}
    C -- 是 --> D[把 V 更新为 B]
    C -- 否 --> E[更新失败]
    D --> F[返回成功]
    E --> G[重新读取或放弃]
    G --> A

CAS 的核心是“比较”和“交换”这两个动作必须是一个不可分割的原子操作。Java 中很多并发类都基于 CAS 实现,例如 AtomicIntegerAtomicLongAtomicReferenceLongAdder

Java 中的使用示例

java
AtomicInteger count = new AtomicInteger(0);

public void add() {
    count.incrementAndGet();
}

incrementAndGet() 内部不是简单的 count = count + 1,而是不断读取旧值、计算新值、尝试 CAS 更新。如果更新失败,说明期间有其他线程修改过,就重新尝试。

CAS 和 synchronized 的区别

对比项CASsynchronized
类型乐观并发控制悲观锁
阻塞通常不阻塞线程竞争失败可能阻塞
适合场景冲突较少、操作很短临界区较大、逻辑复杂
成本失败时自旋重试消耗 CPU阻塞和唤醒有线程调度成本
可读性复杂逻辑较难维护语义更直观

CAS 的三个典型问题

1. ABA 问题

线程 1 读取到值 A,准备 CAS 更新。期间线程 2 把 A 改成 B,又改回 A。线程 1 再执行 CAS 时发现还是 A,于是认为没有变化,但实际上数据已经被修改过。

解决方式:

  1. 使用版本号,每次修改都递增版本。
  2. 使用 AtomicStampedReference 同时比较值和版本。
  3. 对业务对象使用不可变对象或唯一流水号。

2. 自旋开销

如果竞争非常激烈,CAS 会不断失败并重试,线程虽然没有阻塞,但会持续消耗 CPU。

解决方式:

  1. 控制自旋次数。
  2. 降低共享变量竞争。
  3. 使用分段累加,例如 LongAdder
  4. 冲突很高时改用锁。

3. 只能保证单个变量原子更新

普通 CAS 只能更新一个变量。多个变量之间存在一致性要求时,仅靠 CAS 会让代码很复杂。

解决方式:

  1. 封装成一个不可变对象,用 AtomicReference 整体替换。
  2. 使用锁保护多个变量。
  3. 使用事务或数据库约束保证一致性。

选择建议

mermaid
flowchart TD
    A[需要线程安全更新] --> B{临界区是否很短}
    B -- 否 --> C[优先 synchronized 或 Lock]
    B -- 是 --> D{竞争是否激烈}
    D -- 否 --> E[可以使用 Atomic + CAS]
    D -- 是 --> F{是否是计数累加}
    F -- 是 --> G[使用 LongAdder]
    F -- 否 --> C

CAS 很适合简单、高频、低冲突的原子更新;如果业务逻辑包含多步骤判断、多个字段一致性或复杂异常处理,锁通常更清晰。