Skip to content

CAS 与无锁原子基础

一句话定义

CAS(Compare-And-Swap) 是 CPU 提供的原子原语:「只有当内存里的值仍是我看到的旧值时,才写成新值」;是 Atomic*、很多无锁结构的基石,属于乐观并发思路。

可运行对比实验volatile-vs-atomic-vs-lock · VolatileVsAtomicVsLock.java


为什么要学

volatileAtomicIntegerConcurrentHashMap 之前,需要分清:

概念解决什么
volatile可见性 + 部分有序性;保证 count++
CAS / Atomic*单变量的原子读-改-写
synchronized / Lock互斥 + 多步复合逻辑

CAS 在做什么(口语)

比喻:白板上的数字是 10。你和小王都要改成 11:

  1. 你先读:旧值 = 10
  2. 你算:新值 = 11
  3. 你去换:仅当白板上仍是 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 代表AtomicIntegerLongAddersynchronizedReentrantLock
冲突低通常更快略重
冲突高自旋可能更差排队阻塞可能更稳

本文只建立概念;代码对比见 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

和后续专题的关系


复习检查题

  1. CAS 保证的是什么?不保证什么?

    :保证单次「读旧值 → 比较 → 条件成立才写入」在硬件上是原子的,适合单变量读-改-写(如 incrementAndGet)。不保证多字段一致性、长临界区业务逻辑,也不自动解决 ABA;高冲突时只保证正确性,不保证性能(会自旋烧 CPU)。

  2. 为什么 volatile 不能替代 AtomicIntegercount++

    count++ 是读、加、写三步,volatile 只保证每次读/写可见,不保证三步整体互斥。多线程仍会丢更新。AtomicInteger 用 CAS 把「比较旧值再写入」合成一步原子语义。

  3. 高并发计数何时考虑 LongAdder 而不是 AtomicLong

    :写冲突极高、热点集中在少数计数器时,AtomicLong 会大量 CAS 重试。LongAdder分段 Cell 分散写压力,读时求和,以空间换吞吐。读多写少、需要强一致实时读值时仍可能用 AtomicLong 或锁。

  4. 什么是 ABA?什么场景需要关心?

    :线程 A 读到值 V1,期间被改成 V2 又改回 V1,A 的 CAS 仍成功,但中间状态已变。纯数值计数通常无所谓;引用/链表节点等语义下,「对象已被替换又换回来」可能出错,需 AtomicStampedReference 或版本号。

速记

  • CAS = 比较旧值再更新,失败就重试。
  • Atomic* = 乐观、单变量;锁 = 悲观、复合逻辑。
  • 冲突低用 CAS 爽,冲突高要警惕自旋。