在單執行緒中,程式碼看似循序執行,但現代 JVM 與 CPU 為了效能會對指令進行重排序。理解重排序的成因、類型與 Java 記憶體模型(JMM)的約束,是編寫正確並發程式的關鍵。
| 類型 | 發生階段 | 執行主體 | 目的 |
|---|---|---|---|
| 編譯器重排序 | 編譯期 | javac / JIT 編譯器 | 減少暫存器讀寫、指令級並行 |
| 處理器重排序 | 執行期 | CPU(流水線、超標量、亂序執行) | 最大化指令吞吐率 |
| 記憶體系統重排序 | 執行期 | 快取一致性協定(MESI 等) | 快取命中率、總線優化 |
雖然指令被重排,但單執行緒程式的最終結果必須與循序執行一致。這是編譯器與 CPU 遵守的基本契約,保證單執行緒語義不變。
Java 記憶體模型定義了 happens-before 規則,跨執行緒時只有滿足這些規則,寫入才對其他執行緒可見:
StoreStore、StoreLoad、LoadLoad、LoadStore 記憶體屏障)// 錯誤:instance = new Singleton() 非原子,可能發生:
// 1. 分配記憶體 2. 初始化物件 3. instance 指向記憶體
// 重排為 1-3-2 導致其他執行緒看到未初始化完成的 instance
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;
java.util.concurrent 套件(`ConcurrentHashMap`、`CopyOnWriteArrayList`、`AtomicInteger` 等),已正確處理重排序與可見性。jcstress 進行並發測試驗證。