問題描述
- 共享 100 張車票
- 3 個售票窗口(執行緒)同時賣票
- 確保:不重賣、不超賣、線程安全
核心設計
- 實作
Runnable 介面,共享同一實例 → 共享 tickets 欄位
- 使用
synchronized 同步關鍵區段(檢查+賣票+遞減)
- 鎖物件:獨立
String 實例(避免鎖 this 受外部干擾)
完整代碼
class TicketSeller implements Runnable {
private int tickets = 100;
private final Object lock = new Object(); // 專用鎖物件
@Override
public void run() {
while (true) {
synchronized (lock) { // 同步塊:原子化「檢查-賣票」
if (tickets > 0) {
System.out.println(Thread.currentThread().getName()
+ " 賣出第 " + tickets + " 張票");
tickets--;
} else {
break; // 賣完退出迴圈
}
}
// 模擬賣票耗時,釋放鎖讓其他執行緒進入
try { Thread.sleep(10); } catch (InterruptedException ignored) {}
}
}
}
public class TrainStation {
public static void main(String[] args) {
TicketSeller seller = new TicketSeller(); // 單一共享實例
new Thread(seller, "窗口A").start();
new Thread(seller, "窗口B").start();
new Thread(seller, "窗口C").start();
}
}
關鍵點解析
| 技術點 | 說明 |
Runnable vs Thread 繼承 | 實作 Runnable 解耦任務與執行緒,支援共享實例、多重實現 |
| 共享實例 | new Thread(seller) 三次傳入同一 seller → 共享 tickets |
synchronized(lock) | 鎖住 lock 物件,確保同一時間只有一執行緒進入關鍵區 |
| 檢查與遞減原子化 | if (tickets > 0) tickets-- 必須在同一同步塊內,否則競態條件 |
| 專用鎖物件 | new Object() 或 new String(),避免外部代碼鎖 this 造成死鎖/阻塞 |
Thread.sleep | 模擬真實耗時,增加執行緒切換機會,驗證同步正確性 |
常見錯誤與修正
// ❌ 錯誤 1:同步塊只包含賣票,不包含檢查
synchronized(lock) { tickets--; } // 檢查在外面 → 可能賣出負數票
// ❌ 錯誤 2:使用 this 作為鎖,但類可能被外部擴展
synchronized(this) { ... }
// ❌ 錯誤 3:鎖字串常量池物件
synchronized("LOCK") { ... } // 字串常量全域共享,極易死鎖
// ✅ 正確:專用 final Object lock = new Object();
進階:Java 5+ Lock 介面替代
import java.util.concurrent.locks.*;
class TicketSellerLock implements Runnable {
private int tickets = 100;
private final Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName()
+ " 賣出第 " + tickets + " 張票");
tickets--;
} else break;
} finally {
lock.unlock(); // 保證釋放
}
// sleep...
}
}
}
輸出範例(交錯執行)
窗口A 賣出第 100 張票
窗口B 賣出第 99 張票
窗口C 賣出第 98 張票
窗口A 賣出第 97 張票
...
窗口B 賣出第 1 張票
知識延伸
volatile 僅保證可見性,不保證原子性(tickets-- 非原子)
AtomicInteger 可無鎖實現:tickets.getAndDecrement()
- 生產環境:資料庫樂觀鎖/悲觀鎖、分散式鎖