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

Java 並發編程深度解析:volatile 關鍵字詳解

     編輯:Java 程式設計入門

前言

在 Java 並發編程中,volatile 是一種輕量級的同步機制。當變量被聲明為 volatile 類型後,編譯器與運行時都會避免將該變量的操作與其他內存操作重排序。在特定場景下,合理使用 volatile 可替代鎖機制,減少代碼複雜度並提升可讀性。

volatile 核心特性

1. 可見性

當一條線程修改 volatile 變量時,其他線程能立即看到最新值。即讀取 volatile 變量總是返回最近一次寫入的值。

2. 原子性

對單個 volatile 變量的讀寫具有原子性(包括 long 和 double 類型)。但對於 i++ 這類復合操作(讀取-修改-寫入),volatile 不保證 原子性。

使用 volatile 的前提條件

  1. 對變量的寫入操作不依賴當前值,或能確保只有單一線程修改變量值。
  2. 該變量不會與其他狀態變量一起納入不變性條件中。
  3. 訪問變量時不需要加鎖。

volatile 可見性原理:happens-before 關係

volatile 的可見性基於 JMM(Java 內存模型)的 happens-before 規則實現。JMM 定義了八條天然的 happens-before 規則,若兩個操作無法從這些規則推導出順序關係,虛擬機可對其隨意重排序。

其中「volatile 變量規則」規定:對一個 volatile 變量的寫操作,先行發生於後續對該變量的讀操作。此規則保證虛擬機不會對 volatile 讀/寫操作進行重排序。

可見性問題實例演示

public class VolatileTest extends Thread {
    private boolean isRunning = true;

    public boolean isRunning() { return isRunning; }
    public void setRunning(boolean isRunning) { this.isRunning = isRunning; }

    public void run() {
        System.out.println("進入了 run...............");
        while (isRunning) {}
        System.out.println("isRunning 被修改為 false,線程將停止");
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileTest volatileThread = new VolatileTest();
        volatileThread.start();
        Thread.sleep(1000);
        volatileThread.setRunning(false); // 停止線程
    }
}

輸出結果:

進入了 run...............

程序未輸出停止信息,說明主線程修改 isRunning 對工作線程不可見。

原因分析

JMM 規定所有變量存儲在主內存,每條線程有自己的工作內存(緩存副本)。線程操作變量時在工作內存進行,完成後刷新回主內存。例中主線程修改 isRunning 並刷新回主內存,但工作線程仍讀取工作內存中的舊值,導致可見性問題。

解決方案

給 isRunning 加上 volatile 關鍵字。聲明為 volatile 後:

  1. 線程寫入時會立即刷新回主內存。
  2. 其他線程讀取時會先將工作內存中的副本置為無效,再從主內存重新讀取最新值。

volatile 內存語義總結

  1. 寫操作:將修改後的值強制刷新回主內存。
  2. 讀操作:將工作內存中的副本置為無效,從主內存拷貝最新值到工作內存。

volatile 原子性局限性

volatile 不保證復合操作的原子性。示例:

public class VolatileTest1 {
    private volatile int value;

    public void increment() {
        value++;
        System.out.println(value);
    }

    public static void main(String[] args) {
        final VolatileTest1 test = new VolatileTest1();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> test.increment()).start();
        }
    }
}

輸出(示例): 1 6 5 4 2 3 8 7 9 10 — 結果非預期的 1~10 遞增,出現線程安全問題。

原因

value++ 非原子操作,實際分三步:

  1. 讀取 value 到工作內存
  2. 計算 value + 1
  3. 寫回主內存

多線程並發時,線程 1 讀取後未寫回,線程 2 也讀取舊值,導致更新丟失。

解決方案

  • 給 increment() 加鎖(synchronized 或 Lock)
  • 使用原子類(如 AtomicInteger)

synchronized vs volatile 對比

維度volatilesynchronized
修飾範圍只能修飾變量可修飾變量、方法、代碼塊
阻塞性不會導致線程阻塞可能導致線程阻塞
可見性保證保證
原子性不保證復合操作保證
核心解決問題多線程間變量可見性多線程間資源訪問同步性
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved