volatile 是 Java 並發編程中「輕量級同步」的核心關鍵字。它不提供原子性,卻能以極低開銷保證可見性與有序性。本文從 JMM(Java 內存模型)視角,深度剖析其語義、實現原理、適用邊界、常見誤區及進階用法。
volatile 變量:寫入操作強制刷新到主內存,讀取操作強制從主內存加載。任意線程對 volatile 變量的寫入,對後續所有線程的讀取立即可見。volatile 通過內存屏障禁止特定重排序:
volatile 寫前的操作不會被重排到寫後。volatile 讀後的操作不會被重排到讀前。instance 必須 volatile,防止「對象引用賦值」與「對象初始化」重排序導致其他線程拿到半初始化對象。volatile 不保證複合操作原子性。如 volatile int i; i++; 實際是「讀-改-寫」三步,多線程下仍有競態條件。需配合 synchronized、AtomicInteger 或 LongAdder。
Javac 編譯生成字節碼時,針對 volatile 讀寫插入屏障(HotSpot x86 示例):
| 操作 | 插入屏障 | 效果 |
|---|---|---|
| 寫 | StoreStore + StoreLoad | 確保寫前操作不重排到寫後;寫入強制刷新到主存(Lock 前綴指令觸發總線鎖/緩存鎖) |
| 讀 | LoadLoad + LoadStore | 確保讀後操作不重排到讀前;讀取強制從主存加載(使本地緩存行失效) |
volatile boolean shutdownRequested = false;
// 線程 1
while (!shutdownRequested) { doWork(); }
// 線程 2
shutdownRequested = true; // 保證線程 1 立即可見,無需 synchronized
public class Singleton {
private static volatile Singleton instance; // 關鍵!
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查(無鎖,高性能)
synchronized (Singleton.class) {
if (instance == null) { // 第二次檢查(線程安全)
instance = new Singleton(); // 非原子:分配內存 → 初始化 → 賦值引用
}
}
}
return instance;
}
}
public class VolatileCounter {
private volatile int count = 0;
public int increment() {
int current, next;
do {
current = count;
next = current + 1;
} while (!compareAndSet(current, next)); // 需 Unsafe 或 AtomicIntegerFieldUpdater
return next;
}
}
// 實際生產直接用 AtomicInteger / LongAdder
JMM 規定:對 volatile 變量的寫入 happens-before 後續對該變量的讀取。這意味著:
volatile x = 1 前的所有操作(包括非 volatile 變量寫入),對線程 B 讀 x 後的所有操作可見。volatile 充當「同步點」,建立跨線程的時序邊界。| 誤區 | 真相 | 正確做法 |
|---|---|---|
| volatile 保證 i++ 原子 | 不保證,複合操作仍需同步 | 用 AtomicInteger / LongAdder / synchronized |
| volatile 替代 synchronized | 僅適用單變量狀態、單寫多讀、無上下文依賴場景 | 複雜臨界區、多變量不變量必須用 synchronized/Lock |
| volatile 數組元素可見 | volatile int[] arr 只保證引用可見,元素修改不可見 | 用 AtomicIntegerArray / AtomicReferenceArray / CopyOnWriteArrayList |
| volatile 禁止所有重排序 | 只禁止跨越 volatile 讀寫的重排序 | 理解 happens-before 邊界,非相關操作仍可能重排 |
| long/double 需 volatile 保證原子讀寫 | JVM 允許 64 位非原子讀寫(兩次 32 位),volatile 強制原子 | 長整型/雙精度共享變量建議加 volatile |
| 特性 | volatile | synchronized | final |
|---|---|---|---|
| 可見性 | ✓ | ✓ | ✓ (構造器完成前) |
| 有序性 | ✓ (讀寫屏障) | ✓ (鎖語義) | ✗ |
| 原子性 | ✗ | ✓ | ✗ |
| 阻塞 | 不阻塞 | 可阻塞 | 不阻塞 |
| 性能開銷 | 極低(屏障指令) | 較高(上下文切換、鎖升級) | 零(編譯期) |
| 適用場景 | 狀態標記、單寫多讀、DCL | 複雜臨界區、多變量不變量 | 不可變對象、安全發布 |
volatile 是 JMM 向開發者暴露的最輕量級同步原語。它通過內存屏障禁用重排序、強制緩存一致性,以極低開銷實現單變量的可見性與有序性。掌握其「保什麼、不保什麼、怎麼實現、怎麼用、怎麼誤用」,是寫出正確、高性能並發代碼的必修功課。記住:複合操作找 Atomic,臨界區找 synchronized,狀態標記找 volatile,不可變找 final。