在 Java 並發編程中,volatile 是最容易被誤解、誤用的關鍵字。許多開發者以為加上 volatile 就能解決所有多執行緒問題,實則不然。本文從 JVM 記憶體模型出發,徹底釐清 volatile 的真實語義與適用邊界。
先看一個經典反例:
public class Counter { public volatile static int count = 0; // 加上 volatile public static void inc() { try { Thread.sleep(1); } catch (InterruptedException e) {} count++; // 非原子操作! } public static void main(String[] args) { for (int i = 0; i < 1000; i++) { new Thread(Counter::inc).start(); } // 等待所有執行緒結束... System.out.println("最終結果:" + count); // 可能輸出 992、995 等,而非 1000 } }結果: 即使加上
volatile,最終計數仍小於 1000。為什麼?JVM 記憶體模型視角:volatile 真正做了什麼?
根據 JMM (Java Memory Model),執行緒操作共享變量流程:
- Read & Load: 從主記憶體讀取變量副本到工作記憶體
- Use & Assign: 在工作記憶體中運算、修改副本
- Store & Write: 將修改後的副本寫回主記憶體
volatile 的兩大核心語義
| 語義 | 說明 | 效果 |
|---|---|---|
| 可見性 | 寫入 volatile 變量時,強制刷新到主記憶體;讀取時,強制從主記憶體獲取最新值 | 所有執行緒見到的值一致 |
| 禁止指令重排 | 寫入前的操作不會重排到寫入後;讀取後的操作不會重排到讀取前 | 保證偏序關係,雙重檢查鎖單例模式必備 |
count++ 包含三步:read → add → write。volatile 只保證 read 看到最新值 和 write 立即可見,但 無法將三步合併為原子操作。
場景推演:
// 狀態標誌位 - 典型適用場景
volatile boolean shutdownRequested = false;
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) { // 保證可見性,無原子性需求
// 執行任務...
}
}
// 雙重檢查鎖單例 - 依賴禁止指令重排
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 禁止重排保證構造完成再賦值
}
}
}
return instance;
}
}
synchronized 或 AtomicIntegersynchronized 包裝臨界區volatile 完美勝任volatile 是輕量級同步機制,核心價值在於 可見性 與 有序性,不具備原子性。理解 JMM 底層機制,根據業務語義選擇正確工具,才是並發編程的專業態度。