在單線程環境下,變量讀寫是直觀的。但在多線程環境下,JVM 為了性能允許線程將共享變量複製到**工作內存**(CPU 緩存/寄存器)中操作,導致「主內存」與「工作內存」數據不一致。volatile 正是解決此**可見性**問題的關鍵字。
JMM 定義了線程與主內存交互規範:
當一個線程修改 volatile 變量時,會**立即刷新回主內存**;其他線程讀取時,會**強制從主內存讀取最新值**。
class VolatileVisibility {
volatile boolean flag = false;
void writer() { flag = true; } // 立即刷新到主內存
void reader() {
while (!flag) { /* 自旋等待 */ } // 每次從主內存讀取
}
}
volatile 通過**內存屏障**禁止編譯器和處理器對其前後的指令進行重排序:
經典應用:雙重檢查鎖單例模式
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;
}
}
若無 volatile,instance = new Singleton() 可能重排為:分配內存 → 賦值引用 → 初始化,導致其他線程看到「半初始化」對象。
volatile boolean shutdown、volatile int statusflag = true)volatile long、volatile double 保證 64 位讀寫原子性count++、i = i + 1(讀-改-寫非原子)volatile int x; x = x + 1synchronized、Lock、AtomicInteger| 維度 | volatile | synchronized |
|---|---|---|
| 可見性 | ✓ | ✓ |
| 原子性 | ✗(單讀/單寫除外) | ✓ |
| 有序性 | ✓(禁止重排) | ✓ |
| 阻塞性 | 不阻塞 | 可能阻塞 |
| 適用對象 | 變量 | 代碼塊/方法 |
| 性能開銷 | 較低 | 較高 |
JVM 規範允許將 64 位的 long/double 拆分為兩次 32 位操作。未加 volatile 時,多線程環境下可能讀到「半個值」(高 32 位是新值,低 32 位是舊值)。
// 危險:可能讀到撕裂值
long dangerous = 0L;
// 安全:volatile 保證 64 位原子讀寫
volatile long safe = 0L;
volatilesynchronized 或原子類final volatile 用於安全發布不可變對象底層實現依賴 CPU 的 **MESI 協議**(Modified/Exclusive/Shared/Invalid)。volatile 寫入會發送「總線鎖定」或「緩存行失效」消息,強制其他核心緩存行失效,下次讀取時從主內存加載最新值。