---
title: JMM 与 volatile/CAS 底层机制
type: concept
domain: java-concurrency
tags: [jmm, volatile, cas, memory-barrier, mesi, lock-prefix, atomics]
status: learning
created: 2026-09-26
last_reviewed: 2026-09-26
method: feynman
related_questions:
  - "什么是CAS？存在什么问题？"
  - "volatile是如何保证可见性和有序性的？"
  - "有了CAS为啥还需要volatile？"
related_knowledge:
  - ./concurrency-foundations.md
  - ./synchronized-mechanism.md
  - ./aqs-mechanism.md
anki_cards: 6
interview_rounds:
  - "2026-09-26-round-1-Q3"
  - "2026-09-26-round-1-Q4"
  - "2026-09-26-round-1-Q7"
  - "2026-09-29-round-1-Q7"
  - "2026-09-29-round-1-Q8"
  - "2026-09-29-round-1-Q9"
  - "2026-09-29-round-1-Q10"
---

# JMM 与 volatile/CAS 底层机制

> volatile 可见性/有序性的底层机制 + CAS 三痛点 + CAS 与 volatile 的依附关系。是 JMM 的核心。

## 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），两者是"依附 + 互补"而非竞争

## 关键结论

- **结论 A**：volatile 保证可见性的底层是 CPU 层 MESI 协议 + JVM 层 lock 前缀指令，两层缺一不可
- **结论 B**：volatile 保证有序性靠 4 种内存屏障禁止 JIT/CPU 重排，volatile 写后的 StoreLoad 是最贵的
- **结论 C**：CAS 三大痛点——ABA（用 AtomicStampedReference 解决）、自旋开销（用 LongAdder 分段）、单变量原子性（复合操作需外部同步）
- **结论 D**：CAS 是 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 种内存屏障

| 屏障 | 位置 | 作用 |
|------|------|------|
| StoreStore | volatile 写**之前** | 禁止 volatile 写和之前的普通写重排 |
| **StoreLoad** | volatile 写**之后** | 禁止 volatile 写和之后的读重排（最贵） |
| LoadLoad | volatile 读**之后** | 禁止 volatile 读和之后的读重排 |
| LoadStore | volatile 读**之前** | 禁止 volatile 读和之前的写重排 |

#### volatile 三大特性

| 特性 | volatile | synchronized |
|------|----------|--------------|
| 可见性 | ✓ | ✓ |
| 有序性 | ✓ | ✓ |
| 原子性 | ✗（i++ 三步） | ✓（互斥保证整体执行） |

**经典反例**：`volatile int i; i++` 仍不安全，因为 i++ = 读 i + i+1 + 写回，volatile 只保证每步可见，不保证三步互斥。

#### CAS 三要素与三大痛点

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

#### CAS 依附 volatile（关键洞察）

```java
// 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 单例必须同时用。

## 常见误区

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

## JMM 全景：三层结构 + 三大并发问题

### JMM 三层内存结构

| JMM 抽象 | 硬件对应 | 说明 |
|---------|---------|------|
| 主内存（Main Memory） | 物理内存 | 所有线程共享 |
| 工作内存（Working Memory） | CPU 缓存 + 寄存器 | 每线程私有，存主内存变量副本 |
| 处理器缓存（L1/L2/L3） | — | 实现细节，JMM 不暴露 |

**核心语义**：线程只操作工作内存里的变量副本，什么时候刷回主存、什么时候从主存读回来，由 JMM 规则决定。

### JMM 三大并发问题 + 解决手段

| 问题 | 现象 | 解决手段 |
|------|------|---------|
| 原子性 | i++ 拆成读/加/写，中间被中断 | synchronized / Lock / AtomicXxx（CAS）|
| 可见性 | 线程 A 改了对 B 不可见 | volatile（内存屏障）/ synchronized（解锁刷新）|
| 有序性 | 指令重排导致顺序与源码不同 | volatile（屏障禁重排）/ happens-before 规则 |

### 关键概念边界（面试陷阱）

| 概念 | 定位 |
|------|------|
| **JMM** | JLS 定义的**规范**（抽象规则）|
| **JVM** | 具体实现（HotSpot、OpenJ9）|
| **MESI** | 硬件缓存一致性协议 |
| **内存屏障** | CPU 指令序列（JIT 插入）|
| **happens-before** | JMM 提供的可见性契约 |

## happens-before 与 as-if-serial

### 两者对照（面试必考）

| 维度 | happens-before | as-if-serial |
|------|---------------|--------------|
| 作用范围 | **跨线程**的可见性契约 | **单线程内**的优化自由 |
| 目标 | 保证并发正确性 | 保证单线程观察不变 |
| 谁提供 | JMM（Java 规范） | 编译器 + CPU |
| 语义 | "A 的结果 B 一定能看到" | "重排后单线程结果与源码一致" |
| 是否约束重排 | ✅ 禁止跨 hb 边界重排 | ❌ 允许重排 |

**联系**：互补。单线程靠 as-if-serial 兜底（不用 volatile）；跨线程必须建 happens-before 关系。

### happens-before 7 条规则（必背）

1. **程序顺序规则**：单线程内前面 hb 后面
2. **监视器锁规则**：解锁 hb 后续对同一锁的加锁
3. **volatile 规则**：volatile 写 hb 后续 volatile 读
4. **线程启动规则**：start() hb 线程内任何操作
5. **线程终止规则**：线程操作 hb 其他线程检测到终止（join）
6. **线程中断规则**：interrupt() hb 中断线程检测到中断
7. **对象终结规则**：构造函数结束 hb finalize 开始

**传递性**：A hb B 且 B hb C ⇒ A hb C

### as-if-serial 核心语义

- 不管怎么重排，**单线程观察到的执行结果必须与源码顺序一致**
- 例：`int a=0; a=1; println(a);` 打印一定是 1
- **不保证**：变量被其他线程看到的顺序、CPU 指令的实际物理顺序

### 常见坑：double-check 单例必须 volatile

```java
public class Singleton {
    private static volatile Singleton instance;  // ← 必须 volatile
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();  // 三步：分配→初始化→赋值
                }
            }
        }
        return instance;
    }
}
```

`new Singleton()` 编译成三步，as-if-serial 允许重排成"分配 → **赋值** → 初始化"，其他线程可能看到非 null 但字段未初始化的半构造实例。加 volatile 建立 happens-before 关系，禁止这种重排。

## volatile 的语义边界（"单变量一次读写"而非"一段代码"）

### 为什么 volatile 不能保证复合操作原子性

- volatile 的语义边界是"**单个变量的一次读写**"，不是"一段代码块"
- 复合操作（如 `i++`）编译成三步：`读 i` → `加 1` → `写回 i`
- volatile 只能保证每一步对主内存可见，**但中间可能被其他线程插入**
- 例：线程 A 读 i=0，线程 B 也读 i=0，A 写回 1，B 也写回 1 → 结果 1 而非 2（丢失更新）

### 替代方案对照

| 需求 | 手段 |
|------|------|
| 只要求可见性 | volatile |
| 只要求原子性（简单读改写）| AtomicXxx（CAS）|
| 只要求原子性（高并发累加）| LongAdder |
| 要求复合操作原子 | synchronized / ReentrantLock |

### volatile 的正确用法

1. **状态标志位**：`volatile boolean running = true;`（线程循环开关）
2. **double-check locking 单例**：防"new 出对象但字段未初始化"的重排
3. **发布共享引用**：`volatile Handler handler = new Handler();` 发布到线程池

### 屏障插入位置（JMM 规定）

| 位置 | 插入的屏障 | 作用 |
|------|----------|------|
| **volatile 写前** | StoreStore | 保证写之前所有写操作完成 |
| **volatile 写后** | StoreLoad | 保证写操作对其他核立即可见（最贵）|
| **volatile 读前** | LoadLoad | 保证读之后所有操作按序执行 |
| **synchronized 解锁后** | StoreStore + StoreLoad | 保证锁内所有操作在解锁前完成，其他线程加锁可见 |
| **final 字段构造完成后** | StoreStore + LoadStore | 保证 final 字段初始化后发布给其他线程时值不变 |

### x86 vs ARM 内存模型差异

| 维度 | x86（强内存模型）| ARM（弱内存模型）|
|------|------------------|------------------|
| 禁止 LoadStore | 天然禁止 | 需要屏障 |
| 屏障指令 | `mfence` / `sfence` / `lfence` | `dmb` / `dsb` / `isb` |
| volatile 开销 | 大（`lock add` 锁总线）| 小（`dmb ish`）|
| JIT 插入策略 | 少插入，靠硬件 | 多插入，弥补硬件 |

## 延伸追问

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

## 速查表（面试前 30 秒扫完）

```
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 单例必须同时用
```

## 关联题目（题库）

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

## 关联知识

- [并发与并行基础 + 线程安全](./concurrency-foundations.md)
- [synchronized 实现与三特性](./synchronized-mechanism.md)
- [AQS 四大核心机制](./aqs-mechanism.md)

## Anki 候选卡片

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

---

*最后更新：2026-09-26*
