JMM 与 volatile/CAS 底层机制

java-concurrency 📚 learning jmm-volatile-cas · jmm · volatile · cas · memory-barrier · mesi · lock-prefix · atomics

TL;DR(30 秒扫完)

  • volatile = 编译器禁止指令重排 + CPU 加 lock 前缀指令刷缓存(触发 MESI 协议)
  • 4 种内存屏障:StoreStore(volatile 写前)、StoreLoad(volatile 写后,最贵)、LoadLoad(volatile 读后)、LoadStore(volatile 读前)
  • volatile 三大特性:可见性 ✓、有序性 ✓、不保证原子性(i++ 是三步)
  • CAS = Compare And Swap,三要素 expected/actual/new,OS 层 cmpxchg 指令
  • CAS 三大痛点:ABA、自旋开销、单变量原子性
  • CAS 依附 volatile:CAS 在 Java 里就是基于 volatile 实现的(AtomicInteger.value 就是 volatile),两者是"依附 + 互补"而非竞争

关键结论

结论 Avolatile 保证可见性的底层是 CPU 层 MESI 协议 + JVM 层 lock 前缀指令,两层缺一不可
结论 Bvolatile 保证有序性靠 4 种内存屏障禁止 JIT/CPU 重排,volatile 写后的 StoreLoad 是最贵的
结论 CCAS 三大痛点——ABA(用 AtomicStampedReference 解决)、自旋开销(用 LongAdder 分段)、单变量原子性(复合操作需外部同步)
结论 DCAS 是 volatile 的"消费者"——volatile 提供内存屏障让 CAS 读到真实值;volatile 管单操作可见、CAS 管复合操作原子,互补不替代

完整讲解(费曼四步)

STEP 1 · 概念

volatile 是 Java 的并发关键字,保证可见性和有序性但不保证原子性。CAS 是无锁原子操作,靠 CPU 原子指令实现。两者是依附关系:CAS 在 Java 里基于 volatile 实现。

STEP 2 · 大白话

类比 volatile 可见性:想象你和一个同事共用一个白板(主存),每人桌上有一张草稿纸(CPU 缓存)。你改了草稿纸上的字,同事看到的还是他草稿纸上的旧字——这就是可见性问题。
>
volatile 就像规定:"每次改草稿纸,必须同时把白板的字也擦掉重写,并且通知所有同事'你的草稿纸作废了'"——这就是 MESI 协议的失效广播。
>
类比 CAS:你去取快递,柜台说"如果货架上还是你的件(expected=actual),我就给你(改成 new);如果不是,你得重问一次"。这就是 CAS 的"比较并交换"。

STEP 3 · 底层

volatile 可见性的两层机制

层机制作用
CPU 层lock 前缀指令 + MESI 协议写操作强制刷回主存 + 广播失效指令给其他核
JVM/编译器层禁止 store-store 重排 + 禁止 load-load/load-store 重排禁止 JIT 优化把 volatile 操作和前后操作重排
MESI 协议:Modified(已修改)、Exclusive(独占)、Shared(共享)、Invalid(无效)。写 volatile 时,本核从 Modified 变 Invalid 并广播,其他核下次读必须从主存取。

volatile 的 4 种内存屏障

屏障位置作用
StoreStorevolatile 写之前禁止 volatile 写和之前的普通写重排
StoreLoadvolatile 写之后禁止 volatile 写和之后的读重排(最贵)
LoadLoadvolatile 读之后禁止 volatile 读和之后的读重排
LoadStorevolatile 读之前禁止 volatile 读和之前的写重排

volatile 三大特性

特性volatilesynchronized
可见性✓✓
有序性✓✓
原子性✗(i++ 三步)✓(互斥保证整体执行)
经典反例:volatile int i; i++ 仍不安全,因为 i++ = 读 i + i+1 + 写回,volatile 只保证每步可见,不保证三步互斥。

CAS 三要素与三大痛点

痛点描述解决方案
ABAA→B→A,中间被改过又改回,CAS 看不出差异AtomicStampedReference(值+版本号)
自旋开销长时间 CAS 失败占满 CPU,不像锁会阻塞让出LongAdder 分段累加、退避策略
单变量原子性只能保证一个变量,复合操作仍需外部同步synchronized 或复合类型的原子方法

CAS 依附 volatile(关键洞察)

// AtomicInteger 源码
public class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value;  // ← volatile 是关键
    // ...
    public final int incrementAndGet() {
        for (;;) {
            int current = get();  // volatile 读
            int next = current + 1;
            if (compareAndSet(current, next))  // CAS
                return next;
        }
    }
}
依附关系链:volatile 写加 lock 前缀 → CPU 触发 MESI 失效广播 → 其他核下次必须读主存 → CAS 才能读到真实值。 互补关系:
  • volatile 管"单条读/写操作"的可见性
  • CAS 管"读-改-写复合操作"的原子性
  • 一个管"看",一个管"改",谁也替代不了谁

STEP 4 · 简化

一句话总结:volatile 用 MESI + lock 前缀保证可见性、用 4 种内存屏障保证有序性,但不保证原子性;CAS 用 cmpxchg 保证复合原子性,但必须依附 volatile 才能读到真实值。两者互补,DCL 单例必须同时用。

常见误区

volatile 能保证 i++ 线程安全
i++ 是三步,volatile 只保证每步可见,不保证三步互斥
CAS 和 volatile 是竞争关系
是依附 + 互补关系,CAS 基于 volatile 实现
CAS 能保证复合操作原子性
CAS 只能保证单变量原子性,复合操作需外部同步
volatile 只保证可见性
volatile 同时保证可见性和有序性
DCL 单例不需要 volatile
必须加 volatile,否则 new 指令会被重排导致半构造实例
JMM = JVM
JMM 是 JLS 定义的规范,JVM 是实现(HotSpot 等);MESI 是硬件缓存一致性协议,JMM 是软件抽象,互补不替代
JMM 三大问题是"一致性"
是"原子性 / 可见性 / 有序性";"一致性"是 CAP/事务概念
内存屏障是内核指令
是 CPU 指令,由 JIT/编译器显式插入,不是硬件自动

延伸追问

volatile 能保证原子性吗?为什么?
不能。i++ 是读-改-写三步,volatile 只保证单条操作的可见性,不保证三步互斥
CAS 失败后为什么会自旋?自旋期间能看到最新值吗?
能看到。每次重试都是 volatile 读,强制从主存拿最新值
Unsafe 和 VarHandle 的 CAS 实现有什么区别?
Unsafe 是老 API(JDK 9 后私有化)、VarHandle 是新 API(JDK 9 引入、JDK 17 推荐),底层都走 cmpxchg,但 VarHandle 支持 lambda、可组合、更受支持
DCL 单例为什么必须加 volatile?
new 指令会被重排成"分配内存→赋引用→初始化",不加 volatile 时其他线程可能看到"已赋引用但未初始化"的半构造实例

速查表

volatile 底层:
  可见性 = CPU 层 MESI + JVM 层 lock 前缀
  有序性 = 4 种内存屏障(StoreStore/StoreLoad/LoadLoad/LoadStore)
  原子性 = ✗(i++ 不行)

CAS 底层:
  三要素 = expected / actual / new
  OS 指令 = cmpxchg
  三痛点 = ABA / 自旋 / 单变量原子

CAS 依附 volatile:
  AtomicInteger.value 就是 volatile
  volatile 管"看"(可见性+有序性)
  CAS 管"改"(复合原子性)
  互补不替代,DCL 单例必须同时用

Anki 候选卡片

Q: volatile 保证可见性的两层机制?
A: CPU 层 MESI 协议(lock 前缀指令触发缓存失效广播)+ JVM 层禁止 store-store 重排
Q: JMM 定义了哪 4 种内存屏障?
A: StoreStore(volatile 写前)、StoreLoad(volatile 写后,最贵)、LoadLoad(volatile 读后)、LoadStore(volatile 读前)
Q: volatile 能保证 i++ 线程安全吗?
A: 不能。i++ 是三步,volatile 只保证单条操作可见,不保证三步互斥
Q: CAS 的三大痛点?
A: ABA(用 AtomicStampedReference)、自旋开销(用 LongAdder)、单变量原子性(复合操作需外部同步)
Q: CAS 和 volatile 是竞争关系吗?
A: 不是。CAS 基于 volatile 实现,依附+互补关系。volatile 管可见性+有序性,CAS 管复合原子性
Q: DCL 单例为什么必须加 volatile?
A: new 指令会被重排成"分配内存→赋引用→初始化",不加 volatile 其他线程可能看到半构造实例

关联题目

  • [ ] 《什么是CAS?存在什么问题?》— 2026-09-26 Round 1 Q3, ⭐⭐⭐
  • [ ] 《volatile是如何保证可见性和有序性的?》— 2026-09-26 Round 1 Q4, ⭐⭐⭐
  • [ ] 《有了CAS为啥还需要volatile?》— 2026-09-26 Round 1 Q7, ⭐⭐⭐⭐

关联知识

JMM 是 JLS 规范;volatile 靠 MESI+内存屏障保可见性/有序性;CAS 靠 cmpxchg 保复合原子性;happens-before 7 条规则是跨线程可见性契约
JMM 像交通规则(规范),JVM 像具体车辆(实现);MESI 像高速公路广播系统(硬件层);volatile 像红绿灯(禁止重排);CAS 像电梯按钮按了立刻执行(原子)
✦ 记 忆 口 诀 ✦
JMM≠JVM;三大问题=原子性/可见性/有序性(非一致性);volatile 保可见+有序不保原子;happens-before 7 条+传递性
关键可视化
JMM 三层结构 vs 硬件
flowchart TB
  subgraph JMM["JMM 抽象"]
    M["主内存<br/>所有线程共享"]
    W["工作内存<br/>每线程私有"]
  end
  subgraph HW["硬件层"]
    PM["物理内存"]
    CC["CPU 缓存 L1/L2/L3"]
    REG["寄存器"]
  end
  M -.对应.-> PM
  W -.对应.-> CC
  W -.对应.-> REG
  JMM -.-|互补| MESI["MESI 缓存一致性协议"]
  JMM -.-|契约| HB["happens-before 7 条规则"]
volatile 三大特性 + 语义边界
flowchart LR
  V["volatile"] --> A["可见性 ✓<br/>MESI + lock 前缀"]
  V --> O["有序性 ✓<br/>4 种内存屏障禁重排"]
  V --> X["原子性 ✗<br/>i++ 拆三步:读→加→写<br/>每步独立可见,中间可插入"]
  X -.替代.-> L["synchronized / Lock"]
  X -.替代.-> C["AtomicXxx = volatile + CAS"]
  X -.替代.-> D["LongAdder 高并发累加"]
4 种内存屏障 + JMM 插入位置
flowchart TB
  subgraph B["4 种屏障"]
    SS["StoreStore<br/>后续写不能提前"]
    SL["StoreLoad<br/>后续读不能提前到前写<br/>(最贵)"]
    LL["LoadLoad<br/>后续读不能提前"]
    LS["LoadStore<br/>后续写不能提前到前读"]
  end
  subgraph POS["JMM 插入位置"]
    P1["volatile 写前 → StoreStore"]
    P2["volatile 写后 → StoreLoad"]
    P3["volatile 读前 → LoadLoad"]
    P4["synchronized 解锁后 → StoreStore + StoreLoad"]
    P5["final 字段构造后 → StoreStore + LoadStore"]
  end
happens-before vs as-if-serial
flowchart LR
  subgraph HB["happens-before<br/>跨线程可见性契约"]
    R1["1. 程序顺序"]
    R2["2. 监视器锁"]
    R3["3. volatile 读写"]
    R4["4. 线程启动"]
    R5["5. 线程终止"]
    R6["6. 线程中断"]
    R7["7. 对象终结"]
  end
  subgraph AIS["as-if-serial<br/>单线程内优化自由"]
    S1["JIT/CPU 可重排"]
    S2["但单线程观察结果不变"]
  end
  HB -.互补.-> AIS
  AIS -.不保证.-> X["跨线程可见性<br/>需要 happens-before"]
CAS 依附 volatile 的机制链
flowchart LR
  V["volatile 写"] --> L["lock 前缀指令"]
  L --> M["MESI 缓存失效广播"]
  M --> R["其他核下次必须读主存"]
  R --> C["CAS 读到真实值"]
  C --> R2["cmpxchg 原子比较交换"]
  R2 -.失败.-> V
  R2 -.成功.-> DONE["复合操作完成"]
x86 vs ARM 内存模型
flowchart TB
  subgraph X86["x86 强内存模型"]
    X1["天然禁 LoadStore"]
    X2["volatile 只需 lock add"]
    X3["指令少但开销大(锁总线)"]
  end
  subgraph ARM["ARM 弱内存模型"]
    A1["需要显式 dmb"]
    A2["JIT 多插入屏障"]
    A3["指令多但开销小"]
  end
  X86 -.互补.-> ARM
知识关系

⬆️ 前置(Prerequisite)

concurrency-foundations

🔄 延伸(Extends)

synchronized-mechanismaqs-mechanism

⚡ 对比(Contrast)

synchronized— volatile 保证可见性+有序性但不保证原子性;synchronized 三者都保证但代价高AQS 与 AtomicXxx— CAS 是 AtomicXxx 底层;AtomicXxx = volatile 变量 + 无锁 CAS 循环
🎯 概念 📏 规则 ⚠️ 误区 🔍 追问 ✨ 口诀 共 0 张卡,点击翻面