volatile 是 Java 併發編程的輕量級同步機制,保證共享變量的可見性與有序性,但不保證復合操作的原子性。本文從 JMM 內存模型、CPU 緩存一致性協議、內存屏障、實戰陷阱、替代方案五個維度徹底講透。
主內存 (共享堆)
↑ 寫回 (flush) ↓ 讀取 (load)
線程 A 工作內存 線程 B 工作內存
(私有緩存/寄存器) (私有緩存/寄存器)
無 volatile 時:線程修改變量僅在工作內存,何時刷回主內存不可預期,其他線程可能讀到舊值。
| 語義 | 保證 | 底層機制 |
|---|---|---|
| 可見性 | 寫入對所有線程立即可見 | 寫入時刷新緩存行到主內存 + 總線嗅探使其他 CPU 緩存失效 |
| 有序性 | 禁止指令重排序 (LoadLoad, LoadStore, StoreStore, StoreLoad) | 編譯器屏障 + CPU 內存屏障 (Lock 前綴指令) |
對 volatile 變量的讀寫插入內存屏障:
// 寫入屏障 (StoreStore + StoreLoad)
volatile int v = 0;
v = 1; // StoreStore: 之前寫入不重排到之後
// StoreLoad: 寫入不重排到後續讀取之前
// 讀取屏障 (LoadLoad + LoadStore)
int x = v; // LoadLoad: 之後讀取不重排到之前
// LoadStore: 之後寫入不重排到之前讀取之前
Lock 前綴指令:lock addl $0, 0(%rsp) 觸發緩存鎖定,實現屏障效果。
volatile int counter = 0;
// 線程 A // 線程 B
counter++; counter++;
// read counter (0) read counter (0)
// inc (1) inc (1)
// write counter (1) write counter (1)
// 結果: 1 (預期 2) ← 競態條件!
counter++ 包含 讀-改-寫 三步,volatile 僅保證單步可見,無法保證三步原子性。
| 模式 | 代碼 | 適用場景 |
|---|---|---|
| 狀態標誌 | volatile boolean shutdown = false; |
單次寫入、多線程讀取 (關閉信號、初始化完成標誌) |
| 獨立觀察 | volatile int lastValue = sensor.read(); |
寫入不依賴舊值,純賦值 |
| 配合 CAS | AtomicInteger ai = new AtomicInteger(); ai.incrementAndGet(); |
需原子增減、CAS 更新 |
| 雙重檢查鎖單例 | volatile Singleton instance; |
防止指令重排導致半初始化對象可見 |
| 維度 | volatile | synchronized |
|---|---|---|
| 可見性 | ✓ | ✓ (鎖釋放刷新主內存) |
| 有序性 | ✓ | ✓ |
| 原子性 | ✗ (僅單讀/單寫) | ✓ (臨界區互斥) |
| 阻塞 | 不阻塞 | 可能阻塞 (線程掛起/喚醒) |
| 效能 | 極高 (用戶態) | 較低 (內核態切換) |
| 適用範圍 | 單變量狀態 | 復合邏輯、多變量事務 |
x = true、x = y + 1,非 x++、x = x + 1)a + b == c)
import java.lang.invoke.*;
class VolatileExample {
private volatile int value;
private static final VarHandle VH;
static {
try {
VH = MethodHandles.lookup().findVarHandle(VolatileExample.class, "value", int.class);
} catch (Exception e) { throw new ExceptionInInitializerError(e); }
}
public void setVolatile(int newValue) {
VH.setVolatile(this, newValue); // 顯式 volatile 寫入
}
public int getVolatile() {
return (int) VH.getVolatile(this); // 顯式 volatile 讀取
}
public int getAndAdd(int delta) {
return (int) VH.getAndAdd(this, delta); // 原子增減 (CAS)
}
}
優勢:統一 volatile/CAS/普通訪問模式,編譯期類型安全,支援數組/字段/數組元素統一操作。
Atomic*、LongAdder、ConcurrentHashMap 等 JUC 組件// volatile: 可見性保證,非原子 避免後維護誤用