「多窗口售票」是理解 Java 多線程併發、線程安全及同步機制的絕佳入門案例。本文從需求分析到代碼實現,層層遞進講解 synchronized 同步代碼塊如何解決數據競爭,並探討死鎖預防原則。
模擬 4 個窗口同時售賣 100 張票,每張票賣出後總數遞減。初版代碼直接在 run() 循環中操作共享變量 nums:
class Ticket implements Runnable {
private int nums = 100;
@Override
public void run() {
while (true) {
if (nums > 0) {
try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "===" + (nums--));
} else { break; }
}
}
}
問題:nums-- 非原子操作(讀取-修改-寫入),多線程並發下會出現賣出重複票號、負數票號等數據錯誤。
class Ticket implements Runnable {
private int nums = 100;
@Override
public void run() {
while (true) {
synchronized (this) { // 鎖住當前 Ticket 實例
if (nums > 0) {
try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "===" + (nums--));
} else { break; }
}
}
}
}
關鍵點:
this 指向唯一的 Ticket 實例)。sleep() 不釋放鎖,避免在睡眠期間其他線程搶佔修改。
控制台輸出顯示:4 個線程交替輸出,票號從 100 遞減至 1,無重複、無遺漏、無負數,線程安全達標。
代碼註釋提到「避免死鎖問題,同步中嵌套同步,而鎖不同」。核心原則:
ReentrantLock 可中斷鎖/超時機制 作為進階替代。售票案例生動展示了「共享可變狀態 + 非原子操作 = 線程不安全」。synchronized 通過互斥鎖將臨界區串行化,是解決此類問題的基石。實戰中,務必確認鎖對象共享性、同步粒度適中、加鎖順序一致,構建健壯的併發程序。