volatile 是 Java 提供的輕量級同步機制,保證可見性與有序性,但不保證複合操作的原子性(如 i++)。
long/double 64 位讀寫原子性,不保證 i++ 原子性。public class Singleton {
private static volatile Singleton instance; // 關鍵 volatile
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查
synchronized (Singleton.class) { // 加鎖
if (instance == null) { // 第二次檢查
instance = new Singleton(); // 非原子操作:分配內存→初始化→賦值引用
}
}
}
return instance;
}
}
原理:new Singleton() 非原子,可能發生「分配內存→賦值引用→初始化」重排,導致其他線程拿到半初始化對象。volatile 禁止該重排,保證構造完成前引用不可見。
volatile boolean shutdown = false;
// 線程 A
while (!shutdown) { doWork(); }
// 線程 B
shutdown = true; // 保證線程 A 立即看到 true 並退出循環
無 volatile 時,JIT 可能將 shutdown 缓存到寄存器,導致線程 A 永遠不退出。
JVM 允許將 64 位讀寫拆分為兩次 32 位操作,volatile 強制單指令完成。
volatile int count = 0;
// 多線程下 count++ 依然線程不安全!
// 原因:讀取 → 加1 → 寫回 三步非原子,會丟失更新
// 解決:AtomicInteger 或 synchronized
JSR-133 定義:對 volatile 變量的寫入 happens-before 後續對該變量的讀取。即寫入對後續讀線程可見。
編譯器在生成字節碼時,根據操作類型插入屏障禁止重排:
| 操作類型 | LoadLoad | LoadStore | StoreStore | StoreLoad |
|---|---|---|---|---|
| 普通讀/寫 | - | - | - | - |
| volatile 讀 | ✓ | ✓ | - | - |
| volatile 寫 | - | - | ✓ | ✓ |
volatile int v, u;
int a, b;
void f() {
int i = a; // 普通讀
int j = b; // 普通讀
i = v; // volatile 讀 → 前插 LoadLoad + LoadStore
j = u; // volatile 讀 → 前插 LoadLoad + LoadStore
a = i; // 普通寫
b = j; // 普通寫
v = i; // volatile 寫 → 後插 StoreStore
u = j; // volatile 寫 → 後插 StoreStore + StoreLoad (全屏障)
i = u; // volatile 讀 → 前插 LoadLoad + LoadStore
j = b; // 普通讀
a = i; // 普通寫
}
| 維度 | volatile | synchronized |
|---|---|---|
| 原子性 | 單讀寫 | 任意代碼塊 |
| 可見性 | ✓ | ✓ (鎖釋放/獲取) |
| 有序性 | ✓ (屏障) | ✓ (鎖語義) |
| 阻塞 | 不阻塞 | 可能阻塞 |
| 適用場景 | 狀態標誌、單次賦值、DCL | 複合操作、臨界區、複雜同步 |
true/false 切換、賦值常量)。if (a > b))。volatile 是並發編程的「利器」,用對地方可大幅降低同步開銷;用錯地方引發極難排查的並發 Bug。核心記住:只負責可見與有序,不負責原子(除 64 位讀寫)。複雜業務邏輯老誠實用 synchronized、Lock 或原子類。