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

Java 並發核心:volatile 關鍵字原理、應用場景與常見誤區深度解析

     編輯:Java 相關知識

volatile 能做什麼?

volatile 是 Java 提供的輕量級同步機制,保證可見性與有序性,但不保證複合操作的原子性(如 i++)。

  • 可見性:一線程寫入,他線程立即可見(強制刷新主存、失效其他線程緩存)。
  • 有序性:禁止指令重排序(內存屏障)。
  • 原子性:僅保證 long/double 64 位讀寫原子性,不保證 i++ 原子性。

經典應用場景

1. 雙重檢查鎖 (DCL) 單例模式防指令重排

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 禁止該重排,保證構造完成前引用不可見。

2. 實現線程可見性標誌位

volatile boolean shutdown = false;

// 線程 A
while (!shutdown) { doWork(); }

// 線程 B
shutdown = true; // 保證線程 A 立即看到 true 並退出循環

無 volatile 時,JIT 可能將 shutdown 缓存到寄存器,導致線程 A 永遠不退出。

3. 保證 long/double 64 位讀寫原子性

JVM 允許將 64 位讀寫拆分為兩次 32 位操作,volatile 強制單指令完成。

volatile 不能做什麼?

volatile int count = 0;
// 多線程下 count++ 依然線程不安全!
// 原因:讀取 → 加1 → 寫回 三步非原子,會丟失更新
// 解決:AtomicInteger 或 synchronized

底層原理:JMM 與內存屏障

Happens-Before 規則

JSR-133 定義:對 volatile 變量的寫入 happens-before 後續對該變量的讀取。即寫入對後續讀線程可見。

內存屏障插入策略 (JMM)

編譯器在生成字節碼時,根據操作類型插入屏障禁止重排:

操作類型LoadLoadLoadStoreStoreStoreStoreLoad
普通讀/寫----
volatile 讀✓✓--
volatile 寫--✓✓
  • StoreLoad(全屏障,開銷最大):寫 volatile 後插入,確保寫入對其他 CPU 可見。
  • LoadLoad/LoadStore:讀 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 vs synchronized 選型指南

維度volatilesynchronized
原子性單讀寫任意代碼塊
可見性✓✓ (鎖釋放/獲取)
有序性✓ (屏障)✓ (鎖語義)
阻塞不阻塞可能阻塞
適用場景狀態標誌、單次賦值、DCL複合操作、臨界區、複雜同步

安全使用 checklist(必須同時滿足)

  1. 寫入操作不依賴當前值(如 true/false 切換、賦值常量)。
  2. 該變量不參與不變式約束(不與其他變量組成邏輯關係,如 if (a > b))。

總結

volatile 是並發編程的「利器」,用對地方可大幅降低同步開銷;用錯地方引發極難排查的並發 Bug。核心記住:只負責可見與有序,不負責原子(除 64 位讀寫)。複雜業務邏輯老誠實用 synchronized、Lock 或原子類。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved