java併發編程實踐學習(10)死鎖

安全性和活躍度通常相互制約。我們使用鎖來保證線程安全,但是濫用鎖可能引起鎖順序死鎖。

一.死鎖

當一個線程永遠佔有一個鎖,而其他線程嘗試去獲得這個鎖,那麼他們將永遠阻塞。當線程A佔有鎖L時,想要獲得鎖M,但是同時線程B持有M,嘗試獲得L,倆個線程將永遠等待下去這被稱作死鎖(或稱致命擁抱)。

1.鎖順序死鎖

簡單的鎖順序死鎖

public class LeftRightDeadLock{
    private final Object left = new Object();
    private final Object right = new Object();

    public void leftRight(){
        synchronized(left){
            doSomething();  
        }
    }
    public void leftLeft(){
        synchronized(right){
            doSomethingElse();  
        }
    }
}

這裏寫圖片描述

2.動態的順序死鎖

下面的代碼中看似沒有死鎖,但有些巧合下可能發生死鎖。
動態加鎖順序產生死鎖

public void transferMoney(Account fromAccomunt,Account toAccount,DollarAmount amount)throws InsufficientFundsException{
    synchronized(fromAccount){
        synchronized(toAccount){
            if(fromAccount.getBalance().compareTo(amount)<0)
                throws new InsufficientFundsException();
            else{
                fromAccount.debit(amount);
                toAccount.credit(amount);
            }
        }
    }
}

如果倆個線程同時調用transferMoney一個從X向Y轉賬,另一個從Y向X轉賬那麼就會發生死鎖。
爲了解決這個問題我們必須制定鎖的順序。並應用在整個程序中,
制定鎖的順序來避免死鎖

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);
              }
        }
    }
    int fromHash=System.identityHashCode(fromAcct);
    int toHash=System.identityHashCode(toAcct);

    if (fromHash<toHash){
        synchronized (fromAcct){
           synchronized (toAcct){
               new Helper.transfer();
           }
        }
    }else if (fromHash>toHash){
        synchronized (toAcct){
           synchronized (fromAcct){
               new Helper.transfer();
           }
        }         
    }else{
      synchronized (tieLock){
        synchronized (toAcct){
           synchronized (fromAcct){
               new Helper.transfer();
           }
        }
      }  
    }
}

3.協作對象間的死鎖

在持有鎖的時候調用外部方法是在挑戰活躍度問題。外部方法可能會獲得其他鎖(產生死鎖的危險),或者遭遇嚴重超時的阻塞。當你持有鎖的時候會延遲其他試圖獲得該鎖的線程。

4.開放調用

在持有鎖的時候調用另一個外部方法很難進行分析,因此是危險的。當調用的方法不需要持有鎖是,這被稱爲開放調用
在程序中儘量使用開放調用。依賴於開放調用的程序,相比於那些在持有鎖的時候還調用外部方法程序,更容易進行死鎖自由度的分析。

5.資源死鎖

當線程見互相等待對方持有的鎖並且誰都不會釋放自己的鎖時就會發生死鎖,當將線程和等待的目標變成資源時,會發生與之類似的死鎖。
另一種基於資源的死鎖是線程飢餓死鎖。

二.避免和診斷死鎖

如果一個程序一次至多獲得一個鎖,那麼就不會發生死鎖。當然這並不現實。但如果你必須獲得多個鎖,那麼鎖的順序必須是你設計的一部分:儘量減少潛在鎖之間的交互數量,遵守文檔化該鎖順序協議。

1.嘗試定時的鎖

顯示的鎖你可以定義超時時間,在規定時間過後tryLock還沒有獲得鎖就返回失敗。

2.通過線程轉儲分析死鎖

預防死鎖是面臨最大的問題,JVM採用線程轉儲幫助你識別死鎖的發生。線程轉儲也包括鎖的信息,比如鎖由哪個線程獲得,獲得其中的棧結構,以及阻塞線程正在等待的鎖究竟是哪一個。在生成轉儲之前JVM在表示“正在等待”關係的有向圖中搜索循環來尋找死鎖。如果發現了死鎖,它會包括死鎖的識別信息,其中參與了哪些鎖和線程,以及程序中造成不良後果的鎖請求發生在哪裏。

三.其他的活躍度危險

死鎖是主要的活躍度危險,但是也肯能有其他活躍度危險包括:飢餓。丟失信號,和活鎖。

1.飢餓

當線程訪問它所需要的資源時卻被永久拒絕,以至於不能再繼續,這樣就發生了飢餓。

2.弱響應性

不良的鎖管理也可能引起弱響應性。如果一個線程長時間佔用一個鎖,其他想要訪問該容器的線程就必須等待很長時間。

3.活鎖

活鎖是線程中活躍度失敗的另一種形式,儘管沒有阻塞,線程任然不能繼續,應爲它不斷重試相同的操作,卻總是失敗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章