當多個線程共享可變數據時,每個讀或寫該數據的線程都必須執行同步。缺乏同步會導致數據競爭、內存可見性問題,甚至 JVM 優化導致的邏輯錯誤。
public class StopThread {
private static boolean stopRequested; // 非 volatile,無同步
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested) { // 可能永遠讀不到主線程的修改
++i;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true; // 主線程修改,但背景線程可能不可見
}
}
現象:程序永不終止。原因:JIT 編譯器可能將循環優化為 if (!stopRequested) while(true) ...,且無同步時主線程的寫入不保證對背景線程可見。
private static synchronized void requestStop() { stopRequested = true; }
private static synchronized boolean stopRequested() { return stopRequested; }
讀寫方法均加 synchronized,確保互斥訪問與內存可見性(happens-before 關係)。缺點:鎖開銷較大,易產生爭用。
private static volatile boolean stopRequested2;
volatile 保證:
適用場景:單個變量的讀寫、狀態標誌、雙重檢查鎖單例模式。
@Test
public void testSyncMethod() { // 使用 synchronized 方法
Thread t = new Thread(() -> {
while (!stopRequested()) { /* busy */ }
});
t.start();
TimeUnit.SECONDS.sleep(1);
requestStop(); // 1秒後停止,線程正常退出
}
@Test
public void testVolatile() { // 使用 volatile
Thread t = new Thread(() -> {
while (!stopRequested2) { /* busy */ }
});
t.start();
TimeUnit.SECONDS.sleep(1);
stopRequested2 = true; // 正常退出
}
對於 boolean、int、引用類型等單次讀寫原子的操作,volatile 提供的語義已足夠。但若涉及複合操作(如 i++、check-then-act),仍需 synchronized 或 AtomicInteger 等原子類。
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| synchronized 方法/塊 | 複合操作、多變量協同、需要互斥 | 語義完整、可重入 | 性能開銷大、死風險 |
| volatile | 單變量狀態標誌、單次寫入/讀取 | 無鎖、輕量、不阻塞 | 不保證複合操作原子性 |
| Atomic 類 | 計數器、CAS 操作 | 無鎖高性能、API 豐富 | 僅支持單變量原子操作 |
記住:同步不僅是互斥,更是內存可見性的契約。沒有同步,就沒有 happens-before,讀線程看到的可能是陈舊值甚至部分構造的對象。