class Ticket implements Runnable {
private int num = 100;
public void run() {
while (true) {
if (num > 0) {
Thread.sleep(2);
System.out.println(Thread.currentThread().getName() + " sale " + num--);
} else break;
}
}
}
輸出中出現:同一張票(如 8)被賣 4 次,甚至出現負數票(-1, -2)。
if (num > 0) —— 檢查System.out.println(...) —— 輸出(耗時,易被搶佔)num-- —— 遞減(讀取-修改-寫入,非原子)三步組成「檢查-輸出-修改」複合操作,**非原子**,線程間可任意交錯:
線程A: if(8>0) → true
線程B: if(8>0) → true
線程C: if(8>0) → true
線程D: if(8>0) → true
線程A: print 8 → num=7
線程B: print 8 → num=6
線程C: print 8 → num=5
線程D: print 8 → num=4 // 8被賣4次
...
線程X: if(0>0) → false? 但已被搶佔執行print -- → -1
public synchronized void sale() {
if (num > 0) {
try { Thread.sleep(2); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " sale " + num--);
}
}
// run() 中調用 sale()
public void run() {
while (true) {
synchronized (this) { // this 指向共享的 Ticket 實例
if (num > 0) {
try { Thread.sleep(2); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " sale " + num--);
} else break;
}
}
}
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
lock.lock();
try {
if (num > 0) {
try { Thread.sleep(2); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " sale " + num--);
} else break;
} finally {
lock.unlock();
}
}
}
private AtomicInteger num = new AtomicInteger(100);
public void run() {
while (true) {
int current = num.get();
if (current > 0) {
if (num.compareAndSet(current, current - 1)) {
System.out.println(Thread.currentThread().getName() + " sale " + current);
}
// else 重試
} else break;
}
}
num 的代碼路徑必須持有同一把鎖。sleep/println 移出同步塊,縮小臨界區。理解「重複賣票」本質是「檢查-執行」非原子,是 Java 併發編程入門的關鍵一課。