多執行緒環境下,對共享可變數據的非同步訪問會導致嚴重後果——即使該變數是原子讀寫的(如 boolean、int)。核心問題在於可見性:一個執行緒寫入的值,其他執行緒可能看不到最新值。
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) { // 執行緒可能永遠讀不到 true
i++;
System.out.println(i);
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true; // 主執行緒修改
}
}
結果:背景執行緒無限循環,看不到 stopRequested 的變化。原因:JIT 編譯器可能將讀取優化為寄存器快取,或 CPU 快取未同步至主記憶體。
public class StopThread {
private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested()) { // 同步讀取
i++;
System.out.println(i);
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop(); // 同步寫入
}
}
關鍵點:讀寫雙方都必須同步。僅同步寫入或僅同步讀取均無法保證可見性。synchronized 建立 happens-before 關係,確保記憶體可見性與原子性。
public class StopThread {
private static volatile boolean stopRequested; // volatile 保證可見性
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested) { // 直接讀取,無需同步方法
i++;
System.out.println(i);
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true; // 直接寫入
}
}
| 特性 | synchronized | volatile |
|---|---|---|
| 可見性 | ✓ | ✓ |
| 原子性 | ✓ (複合操作) | ✗ (單次讀寫) |
| 阻塞 | 可能阻塞 | 不阻塞 |
| 適用場景 | 臨界區、複合操作 | 單一變數狀態標記 |
| 效能 | 較重 | 極輕量 |
StoreStore、LoadLoad 等內存屏障acquire/release 語義,保證同步塊內操作對其他執行緒可見volatilesynchronized 或 AtomicInteger 等原子類if (!flag) flag = true) → 必須用 synchronized 或 CASThread.stop(),採用標記位 + interrupt() 協作式停止強制釋放所有鎖,可能留下不一致狀態的物件被其他執行緒訪問,導致數據損壞。Thread.interrupt() 僅設定中斷標記,由執行緒自行處理,安全得多。