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

Java 並發編程實戰:鎖順序死鎖避免與雜湊碼排序加鎖策略

     編輯:Java 綜合教學

死鎖經典場景:轉帳業務

public void transferMoney(Account from, Account to, int amount) {
    synchronized(from) {
        synchronized(to) {
            if (from.getBalance() < amount) throw new InsufficientFundsException();
            from.debit(amount);
            to.credit(amount);
        }
    }
}

死鎖發生條件

  • 執行緒 A:`transferMoney(account1, account2, 100)` → 鎖 account1 → 等 account2
  • 執行緒 B:`transferMoney(account2, account1, 100)` → 鎖 account2 → 等 account1
  • **循環等待** → 死鎖

解決方案一:固定全域加鎖順序

// 強制規定:永遠先鎖 id 較小的帳戶
public void transferMoney(Account from, Account to, int amount) {
    Account first = from.getId() < to.getId() ? from : to;
    Account second = from.getId() < to.getId() ? to : from;
    
    synchronized(first) {
        synchronized(second) {
            // 業務邏輯
        }
    }
}

缺點:需業務對象有唯一可比較 ID,侵入業務模型

解決方案二:雜湊碼排序加鎖 (通用、無侵入)

public class DeadLockFreeTransfer {
    private static final Object TIE_LOCK = new Object(); // 加時賽鎖
    
    public void transferMoney(Account from, Account to, int amount) 
            throws InsufficientFundsException {
        
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        
        if (fromHash < toHash) {
            synchronized(from) { synchronized(to) { doTransfer(from, to, amount); } }
        } else if (toHash < fromHash) {
            synchronized(to) { synchronized(from) { doTransfer(from, to, amount); } }
        } else {
            // 雜湊碼碰撞極低概率:加時賽鎖
            synchronized(TIE_LOCK) {
                synchronized(from) { synchronized(to) { doTransfer(from, to, amount); } }
            }
        }
    }
    
    private void doTransfer(Account from, Account to, int amount) 
            throws InsufficientFundsException {
        if (from.getBalance() < amount) throw new InsufficientFundsException();
        from.debit(amount);
        to.credit(amount);
    }
}

關鍵技術點解析

System.identityHashCode()

  • 返回對象的**原始雜湊碼** (同 `Object.hashCode()` 預設實現)
  • **不受業務 `hashCode()` 覆寫影響**
  • JVM 根據對象記憶體地址計算,唯一性高

TIE_LOCK 加時賽機制

  • 雜湊碼碰撞概率極低 (32位整數,數百萬對象才有機會)
  • 碰撞時:先獲取全域 `TIE_LOCK`,再按固定順序加雙重鎖
  • 確保**同一組對象永遠以相同順序加鎖**

內部類 Helper 模式

class Helper {
    public void transfer() { ... } // 捕獲外部 final 參數
}
// 使用:new Helper().transfer();
  • 將業務邏輯封裝,避免重複代碼
  • 內部類可存取外部方法參數 (需 final 或 effectively final)

測試驗證

public static void main(String[] args) {
    Account a1 = new Account(1000);
    Account a2 = new Account(1000);
    ExecutorService pool = Executors.newFixedThreadPool(10);
    
    // 5 個執行緒 A->B
    for (int i = 0; i < 5; i++) {
        pool.execute(() -> transferMoney(a1, a2, 100));
    }
    // 5 個執行緒 B->A
    for (int i = 0; i < 5; i++) {
        pool.execute(() -> transferMoney(a2, a1, 100));
    }
    
    pool.shutdown();
    // 無死鎖,正常完成
}

其他避免死鎖策略

策略原理適用場景
固定加鎖順序業務層面規定順序有自然排序鍵的對象
雜湊碼排序對象身份雜湊碼排序通用、無侵入
tryLock + 超時Lock.tryLock(timeout)可中斷、需超時控制
開放調用持有鎖時不調用外部方法避免嵌套鎖
鎖分級分層加鎖,高層先獲取複雜系統

總結

**雜湊碼排序 + 加時賽鎖** 是解決無自然排序對象死鎖的通用最佳方案:

  1. 使用 `System.identityHashCode()` 獲取穩定排序鍵
  2. 按雜湊碼大小決定加鎖順序
  3. 極低概率碰撞用全域 `TIE_LOCK` 序列化
  4. 內部類封裝業務邏輯避免重複

此模式廣泛應用於資料庫連線池、緩存框架等核心組件中。

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