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 操作和前后操作重排 |
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(关键洞察)
// 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"]
endhappens-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⚡ 对比(Contrast)
synchronized— volatile 保证可见性+有序性但不保证原子性;synchronized 三者都保证但代价高AQS 与 AtomicXxx— CAS 是 AtomicXxx 底层;AtomicXxx = volatile 变量 + 无锁 CAS 循环
🎯 概念
📏 规则
⚠️ 误区
🔍 追问
✨ 口诀
共 0 张卡,点击翻面