JMM 是 Java 併發編程的語義基石,定義了線程之間「何時可見、如何有序」。本文系統梳理 JMM 抽象模型、三類重排序、處理器內存屏障、Happens-Before 規則族,為理解 volatile、synchronized、final、並發容器語義打下堅實基礎。
| 維度 | 共享內存模型 (Java 採用) | 消息傳遞模型 (Go/Erlang/Actor) |
|---|---|---|
| 通信機制 | 隱式:讀/寫公共堆變量 | 顯式:發送/接收消息 |
| 同步機制 | 顯式:程序員加鎖/volatile | 隱式:發送先於接收天然有序 |
| 核心挑戰 | 內存可見性、有序性、原子性 | 死鎖、郵箱溢出、消息丟失 |
Java 線程通信對程序員完全透明,若不理解 JMM 隱式規則,極易遇到「讀不到最新值」「指令重排導致邏輯錯」等詭異 Bug。
┌─────────────────────────────────────┐
│ 主內存 (Main Memory) │ ← 存儲所有共享變量:堆對象、靜態字段、數組元素
│ ┌─────────┐ ┌─────────┐ │
│ │ 共享變量x│ │ 共享變量y│ ... │
│ └─────────┘ └─────────┘ │
└──────────────┬──────────────────────┘
│ 讀/寫
┌───────┴───────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 線程 A │ │ 線程 B │
│ 本地內存 │ │ 本地內存 │
│ (抽象概念) │ │ (抽象概念) │
│ x=1 (副本) │ │ x=0 (副本) │
└─────────────┘ └─────────────┘
| 類型 | 發生階段 | 例子 | JMM 對策 | ||
|---|---|---|---|---|---|
| 編譯器重排序 | 源碼 → 字節碼/機器碼 | 獨立語句調換順序、循環不變量外提 | 禁止特定類型(如 volatile 寫後不得前移) | ||
| 指令級並行重排序 | CPU 流水線動態調度 | Load/Store 亂序執行、分支預測 | 要求編譯器插入內存屏障指令 | ||
| 內存系統重排序 | 緩存一致性協議 (MESI) | 寫緩衝區導致 Store-Load 重排 | 屏障指令強制刷新/失效緩存行 |
| CPU | Load-Load | Load-Store | Store-Store | Store-Load | 數據依賴 |
|---|---|---|---|---|---|
| x86/x64 (TSO) | N | N | N | Y | N |
| ARM/POWER | Y | Y | Y | Y | N |
| IA-64 | Y | Y | Y | Y | N |
| 屏障類型 | 指令示例 | 語義 | 對應 CPU 指令 |
|---|---|---|---|
| LoadLoad | Load1; LoadLoad; Load2 | 確保 Load1 裝載完成才執行 Load2 | lfence / ld.acq |
| StoreStore | Store1; StoreStore; Store2 | 確保 Store1 刷新到內存對其他 CPU 可見才執行 Store2 | sfence / st.rel |
| LoadStore | Load1; LoadStore; Store2 | 確保 Load1 裝載完成才執行 Store2 刷新 | mfence / sync |
| StoreLoad | Store1; StoreLoad; Load2 | 全能屏障:Store1 可見前,禁止 Load2 裝載 | mfence / dmb sy / lock addl |
StoreLoad 開銷最大(需刷寫緩衝區),x86 上 lock addl $0,0(%rsp) 實現;JMM 只在必要處插入(如 volatile 寫後、解鎖前)。
定義:若 A hb B,則 A 的執行結果對 B 可見,且 A 在順序上排在 B 之前。不要求 A 物理執行在 B 之前(允許重排序,只要語義一致)。
volatile 寫 ──HB──→ volatile 讀
│ │
▼ ▼
StoreStore屏障 LoadLoad屏障
StoreLoad屏障 LoadStore屏障
│ │
▼ ▼
禁止 寫寫/寫讀重排 禁止 讀讀/讀寫重排
程序員只需記住 HB 規則,無需背誦底層屏障插入細節。JMM 保證:遵守 HB 規則的程序,在任意硬件/編譯器上表現一致。
// 錯誤:instance = new Singleton() 非原子,分三步:
// 1. allocate memory 2. initialize object 3. reference assign
// 重排序可能 1→3→2,導致其他線程看到非 null 但未初始化完成的對象
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
}
// 修復:volatile 禁止 1→3→2 重排序
private static volatile Singleton instance;
| 特性 | 定義 | 關鍵手段 |
|---|---|---|
| 可見性 | 一線程寫入,另一線程能讀到最新值 | volatile、synchronized、final、HB 規則 |
| 有序性 | 操作執行順序符合預期 | HB 規則、屏障指令、as-if-serial |
| 原子性 | 操作不可分割 | synchronized、Lock、原子類、單次讀寫 volatile/基本類型 |
深入理解 JMM,是從「會用併發 API」進階到「能設計正確併發系統、能診斷疑難併發 Bug」的必經之路。