模型 典型處理器 Store-Load Store-Store Load-Load/Load-Store 備註
TSO x86, SPARC-TSO Y - - 僅允許寫-讀重排
PSO SPARC-PSO Y Y - 允許寫-寫重排
RMO IA64 Y Y Y 允許讀-讀/讀-寫重排
PowerPC PowerPC Y Y Y 最弱,全重排
關鍵點: 所有處理器都允許 Store-Load 重排 (寫緩衝區導致);JMM 在字節碼中插入 內存屏障 抑制非法重排,屏蔽硬件差異。
JMM 設計核心:happens-before 規則
happens-before 是 JMM 向程序員承諾的可見性與有序性契約:若 A hb B,則 A 的寫入對 B 可見,且 A 執行順序在 B 之前。
八大天然 happens-before 規則
- 程序順序規則: 線程內按代碼順序 (控制流順序)。
- 鎖規則:
unlock hb 後續對同一鎖的 lock。
- volatile 規則: 寫 volatile hb 後續讀同一 volatile。
- 線程啟動規則:
Thread.start() hb 線程內任何動作。
- 線程終結規則: 線程內所有動作 hb
Thread.join() 返回。
- 線程中斷規則:
interrupt() hb 被中斷線程檢測到中斷。
- 對象終結規則: 構造函數結束 hb
finalize() 開始。
- 傳遞性: A hb B 且 B hb C → A hb C。
編譯器/處理器優化邊界
JMM 允許不改變單線程語義且正確同步的多線程程序結果的重排序。對於 double pi=3.14; double r=1.0; double area=pi*r*r;,pi 與 r 賦值可重排 (無數據依賴),但 area 必須在兩者之後 (數據依賴)。
JSR-133 (Java 5) 關鍵修補
1. volatile 語義增強:禁止 volatile 與普通變量重排
class FinalDemo {
final int x; // 最終字段
static FinalDemo obj;
void writer() { obj = new FinalDemo(1); } // 構造函數寫 final
void reader() {
FinalDemo o = obj; // 讀引用
if (o != null) System.out.println(o.x); // 保證讀到 1,不會讀到 0
}
}
兩條重排序規則:
- 構造函數內對 final 域寫入,不得重排序到構造函數外 (對其他線程可見)。
- 初次讀對象引用,不得重排序到 final 域讀取之前。
結果:final 具有初始化安全性,無需同步即可安全發布不可變對象。
三類程序的可見性保證對比
| 程序類型 | 順序一致性模型 | JMM | 說明 |
|---|---|---|---|
| 單線程 | 一致 | 一致 | as-if-serial 語義保證 |
| 正確同步多線程 | 一致 | 一致 | JMM 核心承諾:data-race-free → SC |
| 未同步/錯誤同步多線程 | 未定義 | 未定義 | JMM 不承諾任何可見性 |
誤區 正確認知
volatile 保證原子性 volatile 僅保證可見性與有序性,i++ 非原子,需 AtomicInteger 或 synchronized
final 對象不可變 final 引用不可變,但對象內部可變狀態仍可修改,需深度不可變設計
synchronized 只互斥 synchronized 同時包含互斥、可見性 (hb)、有序性 (禁止重排)
無鎖即無序 正確同步 (volatile/final/lock) 即可獲得 JMM 保證的有序性
總結
JMM 是 Java 併發編程的基石契約。理解 happens-before 八大規則、volatile/final 語義增強、處理器模型差異與屏障插入,才能寫出正確、高性能的併發代碼。核心心法:先正確同步 (volatile/final/lock),再追求無鎖優化,切勿在未理解 JMM 前嘗試「聰明」的無鎖技巧。