synchronized 關鍵字僅能同步單一方法或程式碼區塊,當業務邏輯橫跨多個方法(如「查詢餘額 → 提款 → 存款」原子操作)時,標準同步機制無法勝任。本文介紹 Doug Lea 同步工具包中的核心概念,並實作 BusyFlag 實現跨方法鎖定。
10 個 ATM 同時操作同一帳戶:查詢餘額 → 提取 80% → 存回同額。預期餘額不變,實際卻頻現「餘額不足」,因為查詢與提款間隙允許其他線程介入修改餘額。
參考《Java Threads》(Scott Oaks & Henry Wong) 設計,提供可重入的忙碌標誌:
public class BusyFlag {
protected Thread busyFlag = null;
protected int busyCount = 0;
public synchronized void getBusyFlag() {
while (!tryGetBusyFlag()) {
try { wait(); } catch (Exception e) {}
}
}
private synchronized boolean tryGetBusyFlag() {
if (busyFlag == null) {
busyFlag = Thread.currentThread();
busyCount = 1;
return true;
}
if (busyFlag == Thread.currentThread()) {
busyCount++;
return true;
}
return false;
}
public synchronized void freeBusyFlag() {
if (getOwner() == Thread.currentThread()) {
busyCount--;
if (busyCount == 0) {
busyFlag = null;
notify();
}
}
}
public synchronized Thread getOwner() {
return busyFlag;
}
}
關鍵改動:
BusyFlag,提供 lock()/unlock() 委派getAccount() 回傳快取實例,確保系統中同一帳戶僅存在單一物件Collections.synchronizedMap 保護快取集合class Account {
String name;
BusyFlag flag = new BusyFlag();
static Map storage = new HashMap();
static Map accounts = Collections.synchronizedMap(new HashMap());
static {
storage.put("John", new Float(1000.0f));
storage.put("Mike", new Float(800.0f));
}
private Account(String name) { this.name = name; }
public synchronized static Account getAccount(String name) {
if (accounts.get(name) == null) accounts.put(name, new Account(name));
return (Account) accounts.get(name);
}
public synchronized void deposit(float amt) { /* ... */ }
public synchronized void withdraw(float amt) throws InsufficientBalanceException { /* ... */ }
public float getBalance() { /* ... */ }
public void lock() { flag.getBusyFlag(); }
public void unlock() { flag.freeBusyFlag(); }
}
public void run() {
Account acc = Account.getAccount("John");
acc.lock();
try {
float bal = acc.getBalance();
acc.withdraw(bal * 0.8f);
acc.deposit(bal * 0.8f);
} finally {
acc.unlock();
}
}
Account("John") 實例並存,鎖定失效,故採單例快取模式BusyFlag 設為 static,全行所有 ATM 同時間僅能操作一筆帳戶,並行度過低finally 區塊確保鎖釋放,避免死鎖本文實作為教學示範,生產環境建議直接使用 Doug Lea Concurrent 套件(後納入 JDK 1.5 作為 JSR-166),提供工業級的 ReentrantLock、Semaphore、ReadWriteLock 等組件。