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

深入理解 Java volatile 關鍵字:可見性與禁止重排序實戰演示

     編輯:Java 相關知識

前言

volatile 是 Java 並發編程中輕量級的同步機制,主打可見性與禁止指令重排序。許多開發者知道這個關鍵字,卻難以在實際程式中觀察到它的效果。本文透過一個經典的「可見性失效」案例,配合 Python 壓力測試腳本,讓你親眼見證 volatile 的關鍵作用。

問題重現:沒有 volatile 時的可見性失效

public class NoVisibility {
    private boolean ready = false;
    private int num = 0;

    private class ReaderThread extends Thread {
        @Override
        public void run() {
            int count = 0;
            while (!ready) { // 這裡會被 JIT 優化成無限循環讀取暫存器值
                count++;
            }
            System.out.println("num = " + num); // 永遠執行不到這裡
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NoVisibility test = new NoVisibility();
        test.new ReaderThread().start();
        Thread.sleep(100); // 確保讀執行緒先跑起來
        test.num = 43;
        test.ready = true; // 寫入 ready = true
        System.out.println("main thread end");
    }
}

Python 壓力測試腳本

因為 JVM 即時編譯(JIT)優化時機不固定,單次執行可能無法複現。我們用 Python 反覆啟動 JVM 來提高複現機率:

import os
i = 0
while True:
    os.system("java -server NoVisibility")  # -server 觸發 Server VM 深度優化
    i += 1
    print("run count: " + str(i))

關鍵觀察點

  1. -server 參數:啟用 Server VM,進行更激進的優化(如循環不變量外提),大幅提高可見性失效的複現率。
  2. 迴圈內無副作用操作:while (!ready) { count++; } 迴圈體極簡單,JIT 极易將 ready 讀取優化為暫存器快取,導致主執行緒寫入 ready = true 對讀執行緒不可見。
  3. 加入 System.out 即可「修復」?:若迴圈內加入 System.out.println(),因其內部含同步塊,會強制刷新記憶體屏障,意外讓 ready 變可見——這只是副作用,非正解。

正確解決方案:加上 volatile

private volatile boolean ready = false; // 加上 volatile
  • 可見性:寫入 volatile 變數時,JMM 會插入 StoreStore + StoreLoad 屏障,強制將快取刷新到主記憶體;讀取時插入 LoadLoad + LoadStore 屏障,強制從主記憶體讀取最新值。
  • 禁止重排序:編譯器與處理器不會將 volatile 讀寫與其前後的普通讀寫重排序,保證 num = 43 必定在 ready = true 之前對其他執行緒可見。

總結

volatile 適合「一寫多讀」、「狀態標誌」等場景。若涉及複合操作(如 count++),仍需 synchronized 或 AtomicInteger。下次遇到多執行緒狀態不同步,先檢查有無漏加 volatile。

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