[本文是我對Java Concurrency In Practice C10的歸納和總結. ?轉載請注明作者和出處, ?如有謬誤, 歡迎在評論中指正. ]
如果多個線程以不同的順序持有多個鎖, 可能發生死鎖:
?
public class AccountTrans { public void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount) throws InsufficientFundsException { synchronized (fromAccount) { synchronized (toAccount) { if (fromAccount.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else { fromAccount.debit(amount); toAccount.credit(amount); } } } } }
?
transferMoney方法先后鎖定fromAccount和toAccount對象. 如果2個線程以如下的方式調用transferMoney方法:
A: transferMoney(myAccount, yourAccount, 10);?
B: transferMoney(yourAccount, myAccount, 20);
死鎖有可能就會發生.
關鍵在于需要保證以相同的順序獲取多個鎖:
?
public class AccountTrans { // 額外的鎖 private static final Object tieLock = new Object(); public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException { class Helper { public void transfer() throws InsufficientFundsException { if (fromAcct.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else { fromAcct.debit(amount); toAcct.credit(amount); } } } // 計算fromAcct和toAcct的hashCode值 int fromHash = System.identityHashCode(fromAcct); int toHash = System.identityHashCode(toAcct); // 根據hashCode值確定獲取鎖的順序 if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } else { // 當hashCode值相同時, 無法確定fromAcct和頭Acct鎖的獲取順序, 因此增加額外的鎖 synchronized (tieLock) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } } } }?
open call
所謂open call是指在未持有鎖時調用外部方法. 持有鎖的時候調用外部方法, 如果被調用的方法需要獲取其他的鎖, 可能帶來死鎖的風險. 如果被調用的方法發生阻塞, 當前線程將長時間持有鎖, 其他等待獲取該鎖的線程就會被阻塞.
因此我們應該盡量在未持有鎖的時候進行方法的調用.
?
資源死鎖
比如線程A持有數據庫D1的連接, 并等待獲取數據庫D2的連接. 而線程B持有數據庫D2的連接, 并等待獲取數據庫D1的連接. 此時就發生了死鎖.
資源死鎖的另一種形式是線程饑餓死鎖, 參見第八章.
?
避免死鎖
1. 盡量不要同時持有多個鎖.
2. 如果必須同時持有多個鎖, 那么保證以一致的順序獲取鎖.
3. 盡量在未持有鎖的情況下進行方法的調用(open call).