volatile 保證兩項關鍵語義:
volatile 變數的值對其他執行緒立即可見。volatile 讀寫前後的操作不會被編譯器/處理器跨越邊界移動。注意:volatile 不保證原子性(如 volatile int i; i++; 非原子)。
Java 記憶體模型 (JMM) 針對編譯器制定嚴格規則:
volatile 寫 → 禁止重排序(確保前序操作不被移至寫入之後)。volatile 讀 → 禁止重排序(確保後續操作不被移至讀取之前)。volatile 寫後接 volatile 讀 → 禁止重排序。為實現上述語義,JMM 在字節碼生成階段插入屏障:
| 操作 | 插入屏障 | 作用 |
|---|---|---|
| volatile 寫前 | StoreLoad | 防止前序 volatile 讀/寫與當前寫重排 |
| volatile 寫後 | StoreStore | 確保前序普通寫對所有處理器可見 |
| volatile 讀前 | LoadLoad + LoadStore | 防止後續普通讀/寫被移至讀取之前 |
x86 僅允許「寫-讀」重排,因此:
volatile 寫前保留 StoreLoad(lock addl $0,0(%esp))。volatile 寫開銷遠大於讀。舊模型允許 volatile 與普通變數重排,導致「寫-讀」不具備監視器釋放-獲取語義。JSR-133 禁止此類重排,使 volatile 具備與鎖相同的內存語義,提供輕量級執行緒通訊機制。
flag = true、非 i++)。volatile boolean initialized = false;
// Thread 1
context = loadContext();
initialized = true; // 釋放語義,確保 context 可見
// Thread 2
while (!initialized) { Thread.sleep(100); }
doSomethingWithConfig(context); // 獲取語義,保證看到最新 context
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton(); // volatile 禁止指令重排,防止半初始化
}
}
return instance;
}
}
| 維度 | volatile | synchronized |
|---|---|---|
| 原子性 | 單變數讀寫 | 整個臨界區 |
| 適用場景 | 狀態標誌、單次賦值、DCL | 複雜邏輯、多變數不變式 |
| 效能 | 輕量,無上下文切換 | 較重,涉及監視器爭搶 |
⚠️ 切記:volatile無法替代synchronized,濫用會導致難以排查的並發 Bug。