核心結論
volatile 保證:可見性(寫入對其他線程立即可見)、有序性(禁止指令重排序)。
不保證:原子性(複合操作不可分割)。
為什麼不保證原子性?
多 CPU 核心架構下:
- 線程 A 讀取
volatile int x 到寄存器
- 線程 A 修改寄存器值
x+1
- 寫回主存前,線程 B 也讀取了舊值
x
- 線程 B 修改並寫回
x+1
- 線程 A 寫回
x+1(覆蓋 B 的結果)
- 結果:兩次遞增,值只加 1
volatile 強制每次讀寫主存,但「讀-改-寫」三步仍可被交叉執行,無法合併為單一原子指令。
代碼演示
class Counter {
volatile int count = 0;
public void increment() { count++; } // 非原子!
}
// 1000 個線程各遞增 1000 次,預期 1,000,000,實際遠小於此
volatile 適用場景 vs 不適用場景
| 適用 (單一讀/寫) | 不適用 (複合操作) |
狀態標誌:volatile boolean running = true; | 計數器:count++ |
單次賦值:volatile int config = loadConfig(); | 檢查後行動:if (x > 0) x--; |
| 雙重檢查鎖模式中的實例引用 | 累加/累減/複雜邏輯 |
保證原子性的正確方案
- AtomicInteger:
atomic.incrementAndGet() (CAS 底層)
- synchronized:鎖住複合操作
- Lock:
ReentrantLock 精細控制
- ThreadLocal:避免共享
內存模型視角 (JMM)
volatile 插入 LoadLoad、LoadStore、StoreStore 屏障,禁止重排序
- 但無 StoreLoad 屏障(需
synchronized 或 Lock),故無法保證「寫後讀」原子性
- 單個
volatile 讀/寫是原子的 (32位以基本類型),但複合操作非原子
總結
volatile = 輕量級同步,適用於單一變量的狀態標記。任何「讀取-計算-寫入」模式的複合操作,必須用原子類或鎖。誤以為 volatile 能解決計數器線程安全是經典陷阱。
- 上一篇文章: C++ 團隊項目模塊化拼裝:頭文件聲明與獨立編譯實踐
- 下一篇文章: Java 方法參數與泛型參數的區別:泛型是類型參數化機制
Copyright ©
程式碼之家 All Rights Reserved