火車站有 100 張票,三個窗口(執行緒)同時售票。需確保:
class PP implements Runnable {
public int tickets = 100; // 共享票數
String str = new String("123"); // 同步鎖物件
public void run() {
while (true) {
synchronized (str) { // 同步區塊
if (tickets > 0) {
System.out.println(Thread.currentThread().getName()
+ "賣出去的票數是:" + tickets);
tickets--;
} else {
break; // 票賣完,跳出迴圈結束執行緒
}
}
}
}
}
public class Threade_14 {
public static void main(String[] args) {
PP pp = new PP(); // 單一任務實例,共享 tickets
Thread tt1 = new Thread(pp); // 窗口 1
Thread tt2 = new Thread(pp); // 窗口 2
Thread tt3 = new Thread(pp); // 窗口 3
tt1.start();
tt2.start();
tt3.start();
}
}
tickets 為 PP 實例變數Thread 共用同一 PP 實例 ppstatic 或外部容器String str = new String("123"); // 專用鎖物件
synchronized (str) { ... }
String 物件作為鎖,避免鎖住 this 或類字面常數造成外部干擾synchronized(this) 也可行,但若外部程式碼也鎖 pp 實例會擴大競爭範圍synchronized("123") 錯誤:字串常數池可能被其他類別共用,導致非預期阻塞synchronized (str) {
if (tickets > 0) { // 檢查與遞減在同一原子操作中
print...
tickets--;
} else break;
}
Thread-0賣出去的票數是:100 Thread-1賣出去的票數是:99 Thread-2賣出去的票數是:98 Thread-0賣出去的票數是:97 ... Thread-1賣出去的票數是:1 Thread-2賣出去的票數是:0 ← 最後一張票 // 所有執行緒檢查 tickets<=0 後 break 結束
| 錯誤寫法 | 問題 | 修正 |
|---|---|---|
| synchronized(tickets) | 基本型別無法加鎖 | 用物件鎖或 Integer 包裝類(但自動拆箱有風險) |
| if(tickets>0) synchronized {...} | 檢查在鎖外,競態條件 | 檢查移入 synchronized 區塊內 |
| 用 static tickets + synchronized(PP.class) | 可行但耦合度高 | 實例變數 + 實例鎖更符合 OO 封裝 |
| 無 break,賣完繼續跑 | 輸出負數票數 | else break 終止執行緒 |
class TicketSeller implements Runnable {
private final AtomicInteger tickets = new AtomicInteger(100);
public void run() {
while (true) {
int current = tickets.decrementAndGet(); // 原子遞減並取得舊值
if (current >= 0) {
System.out.println(Thread.currentThread().getName()
+ "賣出票號:" + (current + 1) + " 剩餘:" + current);
} else {
tickets.incrementAndGet(); // 還原最後一次遞減
break;
}
}
}
}
AtomicInteger:無鎖原子操作,效能優於 synchronizeddecrementAndGet():原子性「遞減並回傳新值」synchronized 非公平,可用 ReentrantLock(true) 實現公平鎖AtomicInteger 或 LongAdder(Java 8)優於鎖這個經典案例完美展示了「共享可變狀態」下的同步核心原則:封裝共享資料、最小化關鍵區段、原子性檢查與修改。