首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程同步進階:跨方法鎖定與 BusyFlag 實現

     編輯:Java 相關知識

synchronized 關鍵字僅能同步單一方法或程式碼區塊,當業務邏輯橫跨多個方法(如「查詢餘額 → 提款 → 存款」原子操作)時,標準同步機制無法勝任。本文介紹 Doug Lea 同步工具包中的核心概念,並實作 BusyFlag 實現跨方法鎖定。

問題場景:ATM 並發存提款

10 個 ATM 同時操作同一帳戶:查詢餘額 → 提取 80% → 存回同額。預期餘額不變,實際卻頻現「餘額不足」,因為查詢與提款間隙允許其他線程介入修改餘額。

BusyFlag:信號量模式的 Java 實現

參考《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;
    }
}

整合至 Account:實現帳戶級獨佔存取

關鍵改動:

  1. 內建 BusyFlag,提供 lock()/unlock() 委派
  2. 建構子私有化,改用靜態工廠 getAccount() 回傳快取實例,確保系統中同一帳戶僅存在單一物件
  3. 使用 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(); }
}

ATM 客戶端調用模式

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 套件

本文實作為教學示範,生產環境建議直接使用 Doug Lea Concurrent 套件(後納入 JDK 1.5 作為 JSR-166),提供工業級的 ReentrantLock、Semaphore、ReadWriteLock 等組件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved