首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JVM 指令重排序深度解析:編譯期、執行期與 JMM

     編輯:Java 相關知識

為什麼指令不會照你寫的順序執行?

在單執行緒中,程式碼看似循序執行,但現代 JVM 與 CPU 為了效能會對指令進行重排序。理解重排序的成因、類型與 Java 記憶體模型(JMM)的約束,是編寫正確並發程式的關鍵。

三種重排序類型

類型發生階段執行主體目的
編譯器重排序編譯期javac / JIT 編譯器減少暫存器讀寫、指令級並行
處理器重排序執行期CPU(流水線、超標量、亂序執行)最大化指令吞吐率
記憶體系統重排序執行期快取一致性協定(MESI 等)快取命中率、總線優化

As-If-Serial 語義

雖然指令被重排,但單執行緒程式的最終結果必須與循序執行一致。這是編譯器與 CPU 遵守的基本契約,保證單執行緒語義不變。

JMM 與 Happens-Before

Java 記憶體模型定義了 happens-before 規則,跨執行緒時只有滿足這些規則,寫入才對其他執行緒可見:

  1. 程式順序規則:單執行緒內,書寫順序先行發生於後續操作。
  2. 監視器鎖規則:`unlock` 先行發生於後續對同一鎖的 `lock`。
  3. volatile 變數規則:對 `volatile` 寫入先行發生於後續對同一變數的讀取。
  4. 線程啟動規則:`Thread.start()` 先行發生於執行緒內任何操作。
  5. 線程終止規則:執行緒內所有操作先行發生於 `Thread.join()` 返回。
  6. 傳遞性:A hb B 且 B hb C ⇒ A hb C。

volatile 的雙重作用

  • 可見性:強制讀寫主記憶體,不經 CPU 快取。
  • 禁止重排序:寫 `volatile` 前的操作不會被重排到寫後;讀 `volatile` 後的操作不會被重排到讀前。(JMM 插入 StoreStore、StoreLoad、LoadLoad、LoadStore 記憶體屏障)

經典雙重檢查鎖定(DCL)錯誤與修正

// 錯誤: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;

最佳實務建議

  1. 優先使用 java.util.concurrent 套件(`ConcurrentHashMap`、`CopyOnWriteArrayList`、`AtomicInteger` 等),已正確處理重排序與可見性。
  2. 共享可變狀態必須同步:`synchronized`、`ReentrantLock`、`volatile`、`final`(不可變物件天然安全)。
  3. 避免自造鎖與無鎖演算法,除非極度熟悉 JMM 與 CPU 記憶體模型。
  4. 使用 jcstress 進行並發測試驗證。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved