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
}
}
for(;;) 是**熱點循環**,JIT 編譯後將 t1.over、**t2.over 缓存到寄存器**。over = true 寫入主存,**主線程寄存器值未更新**,導致永遠讀到 false。System.out.println 會觸發 **安全點**,強制刷新寄存器,故「神奇地」修復。| 方案 | 代碼 | 原理 | 適用場景 |
|---|---|---|---|
| volatile (推薦) | volatile boolean over; | 禁止指令重排、強制主存讀寫、內存屏障 | 單個標誌位、狀態標記 |
| AtomicBoolean | AtomicBoolean 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; }
}
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
# 觀察主循環編譯後的彙編,確認是否有內存屏障指令 (lock addl $0,0(%rsp) 等)
**「多線程共享變量必須 volatile/atomic/synchronized」**——這是 JMM 的鐵律。JIT 會無情優化掉「看似無用」的內存讀取,**沒有內存屏障,就沒有可見性保證**。