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 規則:
|
規則
|
説明
|
|
程序順序規則
|
同一線程內,按照代碼順序,前面的操作 |
|
|
|
|
鎖規則
|
鎖的釋放操作 |
|
線程啓動規則
|
|
|
線程終止規則
|
線程內的所有操作 |
|
傳遞性規則
|
如果 A |
總結
JMM 保證有序性的核心手段是 禁止指令重排序 和 建立 happens-before 規則:
- 禁止重排序:通過
volatile、synchronized、final等關鍵字插入內存屏障,限制 CPU 和編譯器的重排序行為。 - happens-before 規則:通過定義操作之間的偏序關係,確保多線程環境下操作的可見性和有序性。
理解這些機制,是編寫安全、高效的併發程序的基礎。