1. 程式人生 > >鎖活躍性問題(死鎖,飢餓、活鎖)

鎖活躍性問題(死鎖,飢餓、活鎖)

一、死鎖

        每個人都擁有其他人需要的資源,同時又等待其他人已經擁有的資源,並且每個人在獲取所有需要的資源之前都不會放棄已經擁有的資源。

        當一個執行緒永遠地持有一個鎖,並且其他執行緒都嘗試去獲得這個鎖時,那麼它們將永遠被阻塞,這個我們都知道。如果執行緒A持有鎖L並且想獲得鎖M,執行緒B持有鎖M並且想獲得鎖L,那麼這兩個執行緒將永遠等待下去,這種情況就是最簡單的死鎖形式。其中多個執行緒因為存在環路鎖依賴關係而永遠地等待下去。

         在資料庫系統的設計中考慮了監測死鎖以及從死鎖中恢復,資料庫如果監測到了一組事物發生了死鎖時,將選擇一個犧牲者並放棄這個事物。Java虛擬機器解決死鎖問題方面並沒有資料庫這麼強大,當一組Java執行緒發生死鎖時,這兩個執行緒就永遠不能再使用了,並且由於兩個執行緒分別持有了兩個鎖,那麼這兩段同步程式碼/程式碼塊也無法再運行了----除非終止並重啟應用。

1、判斷死鎖總結:把每個執行緒假象成有向圖中的一個節點,圖中每條邊的關係是:“執行緒A等待執行緒B所佔有的資源”。如果在圖中形成了一條環路,那麼久存在一個死鎖。

2、死鎖的必要條件:

a、互斥條件:一個資源每次只能被一個程序使用
b、請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。
c、不剝奪條件:程序已獲得的資源,在末使用完之前,不能強行剝奪。
d、迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係。

3、死鎖的幾種常見常見

(1)、鎖順序死鎖(LeftRightDeadLock):兩個執行緒以不同的順序 來獲得相同的鎖。

public class LeftRightDeadLock
{
    private final Object left = new Object();
    private final Object right = new Object();
    
    public void leftRight() throws Exception
    {
        synchronized (left)
        {
            Thread.sleep(5000);
            synchronized (right)
            {
                System.out.println("leftRight end!");
            }
        }
    }
    
    public void rightLeft() throws Exception
    {
        synchronized (right)
        {
            Thread.sleep(5000);
            synchronized (left)
            {
                System.out.println("rightLeft end!");
            }
        }
    }
}

如果所有執行緒以固定的順序來獲取鎖,就不會出現死鎖。

如LeftRightDeadLock每個執行緒都需要獲取left和right兩個鎖,如果都是按照left、right或者right、left的順序獲得鎖就不會發生死鎖,一旦一個執行緒按照leftright順序而另外一個rightleft順序就有可能發生死鎖

(2)、動態的鎖順序死鎖(transferMoney)

public static void transferMoney(Account fromAccount,Account toAccount,long amount){
        synchronized (fromAccount){
            synchronized (toAccount){
                fromAccount.debit(amount);
                toAccount.credit(amount);
            }
        }
    }

A:transferMoney(myaccount,youraccount,10)

B:transferMoney(youraccount,myaccount,20)

如果執行時序不當,A可能獲得myaccount鎖並等待youraccount鎖,而B可能獲得youraccount鎖而等待myaccount鎖

(3)、在協作物件之間發生的死鎖

如果在持有鎖時呼叫某個外部方法,那麼可能產生死鎖,因為在這個外部方法中可能會獲取其他的鎖或者阻塞時間過長,導致其他執行緒無法及時獲得當前被持有的鎖。

因此,應該避免鎖所作用的範圍內呼叫外部的方法。

(4)、開發呼叫

減少鎖的範圍,把不需要加鎖的程式碼移除鎖的範圍域,比如,把使用synchronized同步的方法,改成使用synchronized同步部分程式碼塊。

(5)、資源死鎖

二、飢餓

當執行緒由於無法訪問它所需要的資源而不能繼續執行時,就發生了“飢餓”。

例如:如果java應用程式中對執行緒的優先順序使用不當,或者在持有鎖時執行一些無法結束的結構(死迴圈或者無限制地等待某個資源),那麼可能導致飢餓,因為其他需要這個鎖的執行緒將無法得到它。

總結:要避免使用執行緒的優先順序,因為這會增加平臺的依賴性,並導致死鎖。在大多數的併發程式中都使用預設的執行緒優先順序。

三、活鎖

該問題不會阻塞執行緒,但是也不能繼續執行,因為執行緒將不斷重複執行相同的操作,而且總是失敗。

要解決這種活鎖問題,需要在失敗重試機制中引入隨機性。