首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Java JIT 循環優化導致多線程可見性失效:volatile 修復實戰

     編輯:程式設計綜合問答

問題代碼

class A { long value = 0; void add1() { value++; } }
class Test implements Runnable {
    boolean over = false;
    public boolean isOver() { return over; }
    public void setOver(boolean over) { this.over = over; }
    public void run() {
        long start = System.currentTimeMillis();
        do { count++; a.add1(); } while (System.currentTimeMillis() - start < 50);
        setOver(true); // 寫入
    }
}
public static void main() {
    Test t1 = new Test(), t2 = new Test();
    t1.a = t2.a = new A();
    new Thread(t1).start(); new Thread(t2).start();
    for (;;) { // 主線程死循環讀取
        if (t1.isOver() && t2.isOver()) break; // 可能永遠 false
    }
}

根因:JIT 熱點優化

  • 主線程 for(;;) 是**熱點循環**,JIT 編譯後將 t1.over、**t2.over 缓存到寄存器**。
  • 工作線程修改 over = true 寫入主存,**主線程寄存器值未更新**,導致永遠讀到 false。
  • 加 System.out.println 會觸發 **安全點**,強制刷新寄存器,故「神奇地」修復。

修復方案對比

方案代碼原理適用場景
volatile (推薦)volatile boolean over;禁止指令重排、強制主存讀寫、內存屏障單個標誌位、狀態標記
AtomicBooleanAtomicBoolean over = new AtomicBoolean();CAS 原子操作、內存語義同 volatile需原子復合操作
synchronized讀寫均加鎖監視器進出含全屏障臨界區較大
Thread.sleep/yield循環中 Thread.sleep(1)觸發安全點、間接刷新(不保證)測試臨時用,**生產禁用**

正確代碼


class Test implements Runnable {
    private volatile boolean over = false; // 關鍵修復
    // ... 其他不變
    public boolean isOver() { return over; }
    public void setOver(boolean over) { this.over = over; }
}

深入:Java 內存模型 (JMM) 三大特性

  1. 原子性:單個 volatile 讀寫原子(long/double 除外需 volatile)。
  2. 可見性:volatile 寫入 **happens-before** 後續讀取。
  3. 有序性:volatile 禁止指令重排(StoreStore/LoadLoad 屏障)。

驗證方法

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
# 觀察主循環編譯後的彙編,確認是否有內存屏障指令 (lock addl $0,0(%rsp) 等)

**「多線程共享變量必須 volatile/atomic/synchronized」**——這是 JMM 的鐵律。JIT 會無情優化掉「看似無用」的內存讀取,**沒有內存屏障,就沒有可見性保證**。

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