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

Java volatile 關鍵字深度解析:原理、語義與正確使用場景

     編輯:Java 相關知識

前言

當只需同步讀寫一兩個實例域時,synchronized 開銷過大。volatile 提供無鎖的同步訪問機制,是輕量級線程協作利器。掌握它需先理解 Java 內存模型(JMM)與并发三大特性。

一、JMM 與三大特性

Java 內存模型

  • 所有變量存儲在主內存。
  • 每個線程有私有工作內存,操作變量必須在工作內存進行,不能直接操作主內存。
  • 線程間無法直接訪問彼此工作內存,需通過主內存中轉。

原子性

  • 基本類型讀寫(long/double 除外)是原子的。
  • x=10 原子;y=x、x++、x=x+1 非原子(含讀、改、寫多步)。
  • java.util.concurrent.atomic 包提供原子類(AtomicInteger 等),基於 CAS 指令實現高效原子操作。

可見性

  • 一個線程修改共享變量,其他線程能立即看到新值。
  • volatile 變量:寫入強制刷新主內存,讀取強制從主內存加載,保證可見性。
  • 普通共享變量:寫入何時刷主內存不確定,無法保證可見性。

有序性

  • JMM 允許編譯器/處理器重排序指令,單線程不影響結果,多線程可能破壞語義。
  • volatile 禁止指令重排序,提供有序性保證。
  • synchronized/Lock 通過互斥串行執行也保證有序性。

二、volatile 雙重語義

  1. 可見性:寫入立即刷主存,讀取必從主存,其他線程立即可見新值。
  2. 禁止指令重排序:
    • 讀/寫 volatile 變量前的操作,結果已完成且對前序操作可見;
    • 讀/寫 volatile 變量後的操作,確未執行;
    • 優化時不將 volatile 訪問語句與前後語句重排。

三、經典失效案例:volatile 無法保證原子性

public class Test {
    public volatile int inc = 0;
    public void increase() { inc++; } // 非原子:讀-改-寫
    public static void main(String[] args) {
        final Test test = new Test();
        for (int i=0; i<10; i++)
            new Thread(() -> { for (int j=0; j<1000; j++) test.increase(); }).start();
        while (Thread.activeCount() > 1) Thread.yield();
        System.out.println(test.inc); // 輸出 < 10000
    }
}

多線程並發 inc++ 導致更新丟失。volatile 不保證復合操作原子性。

四、volatile 正確使用條件

  1. 寫入操作不依賴當前值(無 i++、i-- 等)。
  2. 該變量不包含在與其他變量的不變式中。

反例:不變式破壞

public class NumberRange {
    private volatile int lower, upper;
    public void setLower(int v) { if (v > upper) throw ...; lower = v; }
    public void setUpper(int v) { if (v < lower) throw ...; upper = v; }
}

線程 A setLower(4)、線程 B setUpper(3) 並發執行,可能通過檢查但最終範圍變為 (4,3),違反 lower <= upper 不變式。此處必須用 synchronized。

五、兩大經典適用場景

1. 狀態標記

volatile boolean shutdownRequested;
public void shutdown() { shutdownRequested = true; }
public void doWork() { while (!shutdownRequested) { /* do stuff */ } }

標記不依賴其他狀態,volatile 簡化同步代碼,性能優於 synchronized。

2. 雙重檢查鎖單例

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

volatile 禁止 new Singleton() 指令重排(分配內存→初始化→賦值引用),防止其他線程看到半初始化對象。雖有微小性能損耗,但保證正確性。

推薦替代:靜態內部類單例(線程安全、延遲初始化、無鎖、代碼簡潔)。

六、總結

volatile 是輕量級同步機制,嚴格遵守「不依賴舊值、無不變式」兩條件時可替代 synchronized。最常用於狀態標記、DCL 單例。其他場景請優先使用 synchronized 或 JUC 工具類,避免微妙並發 Bug。

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