首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java volatile 關鍵字深度解析:可見性 ≠ 原子性

     編輯:Java 相關知識

volatile 的兩大語義

  1. 可見性:寫入後,所有線程立即可見最新值(強制刷新到主記憶體、使其他線程快取失效)
  2. 禁止指令重排:編譯器/CPU 不會將 volatile 讀寫與其前後操作重排序

關鍵誤區:volatile 不保證原子性!複合操作(如 count++)仍需同步或原子類。

記憶體模型視角:為什麼需要 volatile?

JVM 運行時資料區:

  • 堆:共享記憶體,存放物件實例
  • 線程私有棧:每線程有自己的工作記憶體(快取行),存放變數副本

操作流程:

  1. read + load:從主記憶體讀取到工作記憶體
  2. use + assign:在工作記憶體執行運算(可多次)
  3. store + write:寫回主記憶體

問題:非 volatile 變數,線程 A 寫回主記憶體的時間不可控,線程 B 可能仍使用舊副本。

volatile 保證:每次 read 必從主記憶體獲取最新值;每次 write 立即刷回主記憶體。

經典反例:volatile 無法修復 count++ 競態

public class Counter {
    public volatile static int count = 0; // 加了 volatile
    public static void inc() {
        try { Thread.sleep(1); } catch (InterruptedException ignored) {}
        count++; // 非原子:read-load-use-assign-store-write
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) new Thread(Counter::inc).start();
        Thread.sleep(2000);
        System.out.println("結果:" + count); // 多次運行:992, 995, 998... 從未 1000
    }
}

原因:count++ 拆解為三步驟,volatile 只保證每步的可見性,不保證三步為原子單元。線程間交錯導致更新丟失。

正確解決方案

方案代碼適用場景
synchronizedsynchronized(Counter.class){ count++; }簡單、低爭用
AtomicIntegerAtomicInteger count = new AtomicInteger(); count.incrementAndGet();高併發計數器、CAS 無鎖
LongAdderLongAdder count = new LongAdder(); count.increment();極高併發、統計計數

volatile 適用的經典場景

1. 狀態標記(單次寫入、多線程讀取)

volatile boolean shutdown = false;
void shutdown() { shutdown = true; }
void work() { while (!shutdown) { ... } }

2. 雙重檢查鎖 (DCL) 的單例發布

private static volatile Singleton instance;
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) instance = new Singleton();
        }
    }
    return instance;
}

volatile 禁止 new Singleton() 的指令重排(分配記憶體 → 初始化物件 → 賦值引用),避免其他線程看到「半初始化」物件。

3. 延遲初始化的獨立變數

volatile Config config = null;
Config getConfig() {
    Config c = config;
    if (c == null) synchronized (this) { c = config; if (c == null) config = c = new Config(); }
    return c;
}

volatile vs synchronized 對比

維度volatilesynchronized
原子性✗✓
可見性✓✓
有序性✓✓
阻塞不阻塞可能阻塞
適用對象變數方法/代碼塊
效能較高(無鎖)較低(上下文切換)

底層實現:內存屏障

JIT 編譯器為 volatile 讀寫插入內存屏障:

  • 寫屏障:StoreStore + StoreLoad(確保寫入前操作不重排到寫入後、寫入立即刷新)
  • 讀屏障:LoadLoad + LoadStore(確保讀取後操作不重排到讀取前)

總結

  • volatile 是輕量級同步機制,解決可見性與有序性
  • 絕不**用於複合操作的原子性保證**
  • 適用於「單寫多讀」狀態旗標、DCL 單例、獨立觀察變數
  • 複合操作請用 synchronized、java.util.concurrent.atomic、LongAdder、StampedLock 等
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved