多線程之死鎖

一:死鎖問(wèn)題
所謂死鎖是指多個(gè)線程因競(jìng)爭(zhēng)資源而造成的一種僵局(互相等待),若無(wú)外力作用,這些進(jìn)程將無(wú)法向前推進(jìn)。
ps:看著很難懂,下面有代碼解釋
1.死鎖產(chǎn)生的原因
(1)系統(tǒng)資源競(jìng)爭(zhēng)
通常系統(tǒng)中擁有的不可剝奪的資源,其數(shù)量不足以滿(mǎn)足多個(gè)進(jìn)程運(yùn)行的需要,使得進(jìn)程在 運(yùn)行過(guò)程中,會(huì)因爭(zhēng)奪資源而陷入僵局,如磁帶機(jī)、打印機(jī)等。只有對(duì)不可剝奪資源的競(jìng)爭(zhēng) 才可能產(chǎn)生死鎖,對(duì)可剝奪資源的競(jìng)爭(zhēng)是不會(huì)引起死鎖的。
(2)線程推進(jìn)順序非法
進(jìn)程在運(yùn)行過(guò)程中,請(qǐng)求和釋放資源的順序不當(dāng),也同樣會(huì)導(dǎo)致死鎖。例如,并發(fā)進(jìn)程 P1、P2分別保持了資源R1、R2,而進(jìn)程P1申請(qǐng)資源R2,進(jìn)程P2申請(qǐng)資源R1時(shí),兩者都 會(huì)因?yàn)樗栀Y源被占用而阻塞。
(3)信號(hào)量使用不當(dāng)也會(huì)造成死鎖。
進(jìn)程間彼此相互等待對(duì)方發(fā)來(lái)的消息,結(jié)果也會(huì)使得這 些進(jìn)程間無(wú)法繼續(xù)向前推進(jìn)。例如,進(jìn)程A等待進(jìn)程B發(fā)的消息,進(jìn)程B又在等待進(jìn)程A 發(fā)的消息,可以看出進(jìn)程A和B不是因?yàn)楦?jìng)爭(zhēng)同一資源,而是在等待對(duì)方的資源導(dǎo)致死鎖。
2.死鎖產(chǎn)生的必要條件
產(chǎn)生死鎖必須同時(shí)滿(mǎn)足以下四個(gè)條件,只要其中任一條件不成立,死鎖就不會(huì)發(fā)生。

(1)互斥條件:進(jìn)程要求對(duì)所分配的資源(如打印機(jī))進(jìn)行排他性控制,即在一段時(shí)間內(nèi)某 資源僅為一個(gè)進(jìn)程所占有。此時(shí)若有其他進(jìn)程請(qǐng)求該資源,則請(qǐng)求進(jìn)程只能等待。
(2)不剝奪條件:進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能 由獲得該資源的進(jìn)程自己來(lái)釋放(只能是主動(dòng)釋放)。
(3)請(qǐng)求和保持條件:進(jìn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源 已被其他進(jìn)程占有,此時(shí)請(qǐng)求進(jìn)程被阻塞,但對(duì)自己已獲得的資源保持不放。
(4)循環(huán)等待條件:存在一種進(jìn)程資源的循環(huán)等待鏈,鏈中每一個(gè)進(jìn)程已獲得的資源同時(shí)被 鏈中下一個(gè)進(jìn)程所請(qǐng)求。即存在一個(gè)處于等待狀態(tài)的進(jìn)程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的資源被P0占有。
死鎖例子:

//A鎖住了obj1,企圖鎖住obj2
//B鎖住obj2,企圖鎖住obj3
//C鎖住obj3,企圖鎖住obj1
class DeadLock{
    Object obj1;
    Object obj2;
    public DeadLock(Object obj1,Object obj2){
        this.obj1 = obj1;
        this.obj2 = obj2;
    }
    public void ex1(){
        synchronized (obj1){
            System.out.println(Thread.currentThread().getName()+"obj1");
            synchronized (obj2){
                System.out.println(Thread.currentThread().getName()+"obj2");
            }
        }
    }

}
public class ThreadTest {
    public static void main(String[] args) {
        Object obj1 = "obj1";
        Object obj2 = "obj2";
        Object obj3 = "obj3";
        new Thread(()->{new DeadLock(obj1,obj2).ex1();},"A").start();
        new Thread(()->{new DeadLock(obj2,obj3).ex1();},"B").start();
        new Thread(()->{new DeadLock(obj3,obj1).ex1();},"C").start();
    }
}

三:如何避免死鎖
在有些情況下死鎖可以避免
(1).加鎖順序
(2).加鎖時(shí)限
(3).死鎖檢測(cè)
1.加鎖順序
當(dāng)多個(gè)線程需要相同的一些鎖,按照不同的順序加鎖,死鎖就很容易發(fā)生。如果能確保所有的線程按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生。
使用加鎖順序前

Thread1:
         lock A
         lock B
Thread2:
        lock B 
        lock A

使用加鎖順序方法后

Thread 1:
  lock A 
  lock B

Thread 2:
   wait for A
   lock C (when A locked)

Thread 3:
   wait for A
   wait for B
   wait for C

如果一個(gè)線程(比如線程3)需要一些鎖,那么它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之后,才能獲取后面的鎖。

例如,線程2和線程3只有在獲取了鎖A之后才能?chē)L試獲取鎖C(獲取鎖A是獲取鎖C的必要條件)。因?yàn)榫€程1已經(jīng)擁有了鎖A,所以線程2和3需要一直等到鎖A被釋放。然后在它們嘗試對(duì)B或C加鎖之前,必須成功地對(duì)A加了鎖。

按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制。但是,這種方式需要你事先知道所有可能會(huì)用到的鎖(并對(duì)這些鎖做適當(dāng)?shù)呐判?,但總有些時(shí)候是無(wú)法預(yù)知的。
對(duì)于例子中的死鎖問(wèn)題,我們可以用一個(gè)生活中的例子來(lái)解決,銀行轉(zhuǎn)賬是一個(gè)很常見(jiàn)的場(chǎng)景。其中涉資到同時(shí)申請(qǐng)兩個(gè)鎖的方法,對(duì)于轉(zhuǎn)錢(qián)和收錢(qián)的人,可以使他們對(duì)賬號(hào)的加鎖順序一致,從而避免死鎖。比如:先獲取卡號(hào)大的的賬戶(hù)的鎖。
2.加鎖時(shí)限
通俗說(shuō)就是設(shè)定個(gè)超時(shí)時(shí)間,比如Timeout(超時(shí)時(shí)間)=5秒,這樣如果某個(gè)線程5秒內(nèi)沒(méi)有獲得預(yù)期的鎖,執(zhí)行超時(shí)對(duì)應(yīng)的業(yè)務(wù)邏輯(回退等)。除判斷死鎖外,在很多線程獲取同一資源時(shí),則這些線程超時(shí)的概率就會(huì)很高。
這種機(jī)制存在一個(gè)問(wèn)題,在Java中不能對(duì)synchronized同步塊設(shè)置超時(shí)時(shí)間。你需要?jiǎng)?chuàng)建一個(gè)自定義鎖,或使用Java5中java.util.concurrent包下的工具。
3.死鎖檢測(cè)
死鎖檢測(cè)是一個(gè)更好的死鎖預(yù)防機(jī)制,它主要是針對(duì)那些不可能實(shí)現(xiàn)按序加鎖并且鎖超時(shí)也不可行的場(chǎng)景。

每當(dāng)一個(gè)線程獲得了鎖,會(huì)在線程和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)中(map、graph等等)將其記下。除此之外,每當(dāng)有線程請(qǐng)求鎖,也需要記錄在這個(gè)數(shù)據(jù)結(jié)構(gòu)中。當(dāng)一個(gè)線程請(qǐng)求鎖失敗時(shí),這個(gè)線程可以遍歷鎖的關(guān)系圖看看是否有死鎖發(fā)生。例如,線程A請(qǐng)求鎖2,但是鎖2這個(gè)時(shí)候被線程B持有,這時(shí)線程A就可以檢查一下線程B是否已經(jīng)請(qǐng)求了線程A當(dāng)前所持有的鎖。如果線程B確實(shí)有這樣的請(qǐng)求,那么就是發(fā)生了死鎖(線程A擁有鎖1,請(qǐng)求鎖2;線程B擁有鎖2,請(qǐng)求鎖1)。

當(dāng)然,死鎖一般要比兩個(gè)線程互相持有對(duì)方的鎖這種情況要復(fù)雜的多。線程A等待線程B,線程B等待線程C,線程C等待線程D,線程D又在等待線程A。線程A為了檢測(cè)死鎖,它需要遞進(jìn)地檢測(cè)所有被B請(qǐng)求的鎖。從線程B所請(qǐng)求的鎖開(kāi)始,線程A找到了線程C,然后又找到了線程D,發(fā)現(xiàn)線程D請(qǐng)求的鎖被線程A自己持有著。這是它就知道發(fā)生了死鎖。

下面是一幅關(guān)于四個(gè)線程(A,B,C和D)之間鎖占有和請(qǐng)求的關(guān)系圖。像這樣的數(shù)據(jù)結(jié)構(gòu)就可以被用來(lái)檢測(cè)死鎖。


圖片.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容