Java 內存模型(JMM)主要通過 禁止指令重排序建立 happens-before 規則 來保證有序性,核心手段包括 volatile 關鍵字、synchronized 關鍵字和 final 關鍵字等。

一、有序性問題的根源

有序性問題的根源是 CPU 指令重排序。為了優化性能,CPU 會在不影響單線程執行結果的前提下,調整指令的執行順序。例如:

int a = 1;   // 操作 1
int b = 2;   // 操作 2
int c = a + b; // 操作 3

單線程下,CPU 可能調整為操作 2 → 操作 1 → 操作 3,結果依然是 c=3,不影響正確性。但多線程下,重排序可能破壞依賴關係,導致錯誤。

二、JMM 保證有序性的核心機制

1. 內存屏障(Memory Barrier)

JMM 規定了 內存屏障 來禁止特定類型的指令重排序。內存屏障是一種特殊的 CPU 指令,它會強制 CPU 執行完屏障之前的所有指令,再執行屏障之後的指令,同時確保屏障前後的內存操作可見。

常見的內存屏障類型:

屏障類型

作用

LoadLoad 屏障

確保屏障前的加載指令(Load)先於屏障後的加載指令執行。

StoreStore 屏障

確保屏障前的存儲指令(Store)先於屏障後的存儲指令執行。

LoadStore 屏障

確保屏障前的加載指令先於屏障後的存儲指令執行。

StoreLoad 屏障

確保屏障前的存儲指令先於屏障後的加載指令執行(最嚴格,會刷新緩存)。

2. volatile 關鍵字的有序性保證

volatile 關鍵字通過 插入內存屏障 來禁止重排序。JMM 規定:

  • 當變量被 volatile 修飾時,編譯器和 CPU 必須在該變量的讀寫操作前後插入特定的內存屏障,防止其與其他指令重排序。

具體規則:

  • 寫操作:在 volatile 變量的寫操作後插入 StoreStore 屏障StoreLoad 屏障,確保寫操作對其他線程可見。
  • 讀操作:在 volatile 變量的讀操作前插入 LoadLoad 屏障LoadStore 屏障,確保讀操作能獲取到最新的值。

示例:

volatile int flag = 0;

// 線程 1
flag = 1; // 寫操作,插入 StoreStore + StoreLoad 屏障

// 線程 2
if (flag == 1) { // 讀操作,插入 LoadLoad + LoadStore 屏障
    // ...
}

volatile 禁止了 flag 變量的讀寫操作與其他指令的重排序,保證了多線程下的有序性。

3. synchronized 關鍵字的有序性保證

synchronized 關鍵字通過 互斥執行happens-before 規則 保證有序性。

  • 互斥執行synchronized 修飾的同步塊同一時間只能被一個線程執行,因此同步塊內的指令不會與其他線程的指令交錯執行,間接保證了有序性。
  • happens-before 規則
  • 線程 A 釋放鎖時的操作 happens-before 線程 B 獲取鎖後的操作。
  • 這意味着線程 A 在同步塊內的所有操作,對線程 B 都是可見的,且執行順序不會被重排序。

示例:

synchronized (lock) {
    // 同步塊內的指令不會被重排序,且對其他線程可見
    a = 1;
    b = 2;
}
4. final 關鍵字的有序性保證

final 關鍵字通過 禁止重排序初始化過程 來保證有序性。

  • 對於 final 修飾的變量,編譯器會確保:
  • 變量的初始化完成後,才能被其他線程訪問。
  • 禁止將 final 變量的初始化與其他指令重排序,避免出現“半初始化”狀態。

示例:

public class FinalExample {
    private final int x;

    public FinalExample() {
        x = 1; // final 變量初始化
    }
}

其他線程訪問 x 時,x 一定已經被初始化為 1,不會出現 x 未初始化的情況。

5. happens-before 規則

JMM 定義了 happens-before 規則,用於判斷多線程操作的執行順序。如果操作 A happens-before 操作 B,則:

  • 操作 A 的執行結果對操作 B 可見。
  • 操作 A 的執行順序在操作 B 之前。

常用的 happens-before 規則:

規則

説明

程序順序規則

同一線程內,按照代碼順序,前面的操作 happens-before 後面的操作。

volatile 變量規則

volatile 變量的寫操作 happens-before 後續的讀操作。

鎖規則

鎖的釋放操作 happens-before 後續的獲取鎖操作。

線程啓動規則

Thread.start() 方法 happens-before 線程內的所有操作。

線程終止規則

線程內的所有操作 happens-before 線程的終止檢測(如 Thread.join())。

傳遞性規則

如果 A happens-before B,且 B happens-before C,則 A happens-before C。

總結

JMM 保證有序性的核心手段是 禁止指令重排序建立 happens-before 規則

  • 禁止重排序:通過 volatilesynchronizedfinal 等關鍵字插入內存屏障,限制 CPU 和編譯器的重排序行為。
  • happens-before 規則:通過定義操作之間的偏序關係,確保多線程環境下操作的可見性和有序性。

理解這些機制,是編寫安全、高效的併發程序的基礎。