【Java多線程】死鎖的原因、必要條件、檢測、預防、解除

目錄

1 前言

2 死鎖產生的原因

2.1 競爭資源引起進程死鎖 

2.2 進程推進順序不當引起死鎖 

3 死鎖產生的必要條件

4 處理死鎖的基本方法

5 死鎖的預防方法

5.1 摒棄“請求和保持”條件 

5.2 摒棄“不剝奪”條件 

5.3 摒棄“環路等待”條件 

5.4 系統安全狀態與銀行家算法

6 死鎖的檢測

7 死鎖的解除


1 前言

所謂死鎖(Deadlock),是指多個進程在運行過程中因爭奪資源而造成的一種僵局(DeadlyEmbrace),當進程處於這種僵持狀態時,若無外力作用,它們都將無法再向前推進,同時它們所佔用的cpu、內存等資源也得不到釋放。

2 死鎖產生的原因

2.1 競爭資源引起進程死鎖 

非剝奪性資源,當系統把這類資源分配給某進程後,再不能強行收回,只能在進程用完後自行釋放,如磁帶機、打印機等。

在系統中所配置的非剝奪性資源,由於它們的數量不能滿足諸進程運行的需要,會使進程在運行過程中,因爭奪這些資源而陷入僵局。例如,系統中只有一臺打印機 R1和一臺磁帶機 R2,可供進程 P1和 P2共享。當執行打印操作時,進程只有同獲取到R1和R2資源才能打印成功,否則會一直阻塞直到獲取R1、R2成功。假定 P1已佔用了打印機 R1,P2已佔用了磁帶機 R2。此時,若 P2繼續要求打印機,P2將阻塞;P1若又要求磁帶機,P1也將阻塞。於是,在 P1與P2之間便形成了僵局, 兩個進程都在等待對方釋放出自己所需的資源。 但它們又都因不能繼續獲得自己所需的資源而不能繼續推進, 從而也不能釋放出自己已佔有的資源,以致進入死鎖狀態。

2.2 進程推進順序不當引起死鎖 

典型的,若有兩個獨佔資源如鎖L1、L2,還有進程P1、P2,進程P1和P2都對L1和L2發起請求,若請求鎖的順序如下則會發生死鎖:

P1獲取鎖L1
P2獲取鎖L2
P1請求鎖L2    // P1阻塞
P2請求鎖L1    // P2阻塞

3 死鎖產生的必要條件

雖然進程在運行過程中可能發生死鎖,但死鎖的發生也必須具備一定的條件。綜上所述不難看出,死鎖的發生必須具備下列四個必要條件。 

  1. 互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。如果此時還有其它進程請求該資源,則請求者只能等待,直至佔有該資源的進程用畢釋放。 
  2.  請求和保持條件:指進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源又已被其它進程佔有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。 
  3. 不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。 
  4. 環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0, P1, P2, …, Pn}中的 P0正在等待一個 P1佔用的資源; P1正在等待 P2佔用的資源, ……,Pn正在等待已被 P0佔用的資源。

4 處理死鎖的基本方法

爲保證系統中諸進程的正常運行,應事先採取必要的措施,來預防發生死鎖。在系統中已經出現死鎖後,則應及時檢測到死鎖的發生,並採取適當措施來解除死鎖。目前,處理死鎖的方法可歸結爲以下四種: 

(1) 預防死鎖。這是一種較簡單和直觀的事先預防的方法。該方法是通過設置某些限制條件,去破壞產生死鎖的四個必要條件中的一個或幾個條件,來預防發生死鎖。預防死鎖是一種較易實現的方法,已被廣泛使用。但由於所施加的限制條件往往太嚴格,因而可能會導致系統資源利用率和系統吞吐量降低。 

(2) 避免死鎖。該方法同樣是屬於事先預防的策略,但它並不須事先採取各種限制措施去破壞產生死鎖的四個必要條件,而是在資源的動態分配過程中,用某種方法去防止系統進入不安全狀態,從而避免發生死鎖。這種方法只需事先施加較弱的限制條件,便可獲得較高的資源利用率及系統吞吐量,但在實現上有一定的難度。目前在較完善的系統中常用此方法來避免發生死鎖,如Java中採用synchronized關鍵字來實現線程安全機制。

(3) 檢測死鎖。這種方法並不須事先採取任何限制性措施,也不必檢查系統是否已經進入不安全區,而是允許系統在運行過程中發生死鎖。但可通過系統所設置的檢測機構,及時地檢測出死鎖的發生,並精確地確定與死鎖有關的進程和資源; 然後,採取適當措施,從系統中將已發生的死鎖清除掉。

(4) 解除死鎖。這是與檢測死鎖相配套的一種措施。當檢測到系統中已發生死鎖時,須將進程從死鎖狀態中解脫出來。常用的實施方法是撤消或掛起一些進程,以便回收一些資源,再將這些資源分配給已處於阻塞狀態的進程,使之轉爲就緒狀態,以繼續運行。死鎖的檢測和解除措施有可能使系統獲得較好的資源利用率和吞吐量,但在實現上難度也最大。 

5 死鎖的預防方法

預防死鎖的方法是使四個必要條件中的第 2、3、4 個條件之一不能成立,來避免發生死鎖。至於必要條件 1,因爲它是由設備的固有特性所決定的,不僅不能改變,還應加以 保證。 

5.1 摒棄“請求和保持”條件 

要求進程要麼一次性申請到所有資源去執行,要麼不持有獨佔資源地進入阻塞狀態。以2.1中場景爲例,若系統有足夠的資源分配給某進程,便可把其需要的所有資源分配給該進程,這樣,該進程在整個運行期間便不會再提出資源要求,從而摒棄了請求條件。在分配資源時,只要有一種資源不能滿足某進程的要求,即使其它所需的各資源都空閒,也不分配給該進程,而讓該進程等待。由於在該進程的等待期間,它並未佔有任何資源,因而也摒棄了保持條件。

這種預防死鎖的方法其優點是簡單、易於實現且很安全。但其缺點卻也極其明顯:

  1. 可能造成資源的浪費。比如進程獲取到了打印機和磁帶機,但打印機只需執行1s,而磁帶機要執行10min,這樣任務才能完成,那在執行磁帶機期間進程依然獨佔打印機資源,若其它進程只需要使用打印機,則會造成打印機資源的浪費。
  2. 會使進程延遲運行。因爲僅當進程在獲得了其所需的全部資源後,才能開始運行,但可能因有些資源已長期被其它進程佔用而致使等待該資源的進程遲遲不能運行。 

5.2 摒棄“不剝奪”條件 

在採用這種方法時系統規定,進程是逐個地提出對資源的要求的。當一個已經保持了某些資源的進程,再提出新的資源請求而不能立即得到滿足時,必須釋放它已經保持了的所有資源,待以後需要時再重新申請,這意味着進程不能持有資源進入阻塞或等待狀態。這意味着某一進程已經佔有的資源,在運行過程中會被暫時地釋放掉,也可認爲是被剝奪了,從而摒棄了“不剝奪”條件。 

比如Java中,在synchronized代碼塊中使用wait()使線程等待的同時也會釋放鎖,若等待不釋放鎖的話,該鎖永遠得不到釋放,嘗試獲取該鎖的線程也會永遠等待。

這種預防死鎖的方法實現起來比較複雜且要付出很大的代價,因爲:

  1. 被迫釋放資源可能造成釋放進程前段工作失效。一個資源在使用一段時間後,它的被迫釋放可能會造成前段工作的失效,即使是採取了某些防範措施,也還會使進程前後兩次運行的信息不連續。如進程P1使用打印機打印一段信息後,中途因爲請求其他資源而釋放了打印機,此時打印機又被進程P2獲取輸出了另一段信息,後面P1再獲取時因爲P2在前面已經打印了信息,所以信息不連續了。
  2. 種策略還可能因爲反覆地申請和釋放資源,致使進程的執行被無限地推遲,這不僅延長了進程的週轉時間,而且也增加了系統開銷,降低了系統吞吐量。 

5.3 摒棄“環路等待”條件 

系統將所有資源按類型進行線性排隊,並賦予不同的序號。例如,令輸入機的序號爲 1,打印機的序號爲 2,磁帶機爲 3,磁盤爲 4。所有進程對資源的請求必須嚴格按照資源序號遞增的次序提出,這樣,在所形成的資源分配圖中,不可能再出現環路,因而摒棄了“環路等待”條件。

例如,在2.1場景中,打印機序號爲1,磁帶機序號爲2,進程必須先申請打印機再申請磁帶機。這樣若P1獲取了打印機,P2再獲取打印機會阻塞,不會出現P1獲取了打印機,P2獲取了磁帶機的情況。

這種預防死鎖的策略與前兩種策略比較,其資源利用率和系統吞吐量都有較明顯的改善。但也存在下述嚴重問題: 

  1. 爲系統中各類資源所分配(確定)的序號必須相對穩定, 這就限制了新類型設備的增加。
  2. 可能造成對資源的浪費。例如,某進程先用磁帶機,後用打印機,但按系統規定,該進程應先申請打印機而後申請磁帶機,致使先獲得的打印機被長時間閒置。 
  3. 這種按規定次序申請的方法,會限制用戶簡單、自主地編程。

5.4 系統安全狀態與銀行家算法

在多進程同時請求公共資源的前提下,安全狀態指系統一定不會進入死鎖狀態,不安全狀態指系統可能進入死鎖狀態(不是一定)。

最有代表性的避免死鎖的算法,是 Dijkstra 的銀行家算法。爲實現銀行家算法,系統中必須設置若干數據結構。 銀行家算法參考:https://blog.csdn.net/qq_34039868/article/details/105349138

6 死鎖的檢測

死鎖檢測有如下步驟,

(1) 可利用資源向量 Available,它表示了 m 類資源中每一類資源的可用數目。 
(2) 把不佔用資源的進程(向量 Allocationi:=0)記入 L 表中,即 Li∪L。 
(3) 從進程集合中找到一個 Requesti≤Work 的進程,做如下處理,直到所有進程都被加入到L表中: 
      ① 將其資源分配圖簡化,釋放出資源,增加工作向量 Work:=Work + Allocation i。 
      ② 將它記入 L 表中。 
(4) 若不能把所有進程都記入 L 表中, 便表明系統狀態 S 的資源分配圖是不可完全簡化
的。因此,該系統狀態將發生死鎖。 

僞代碼如下:

Work:=Available; 
   L:={Li |Allocation i=0∩Request i=0} 
   for all Li ∉L  do 
     begin 
       for all Request i≤Work do 
         begin 
          Work :=Work + Allocation i; 
          Li∪L; 
         end 
       end 
     deadlock :=┓(L={p1,p2,…,pn}); 

7 死鎖的解除

當發現有進程死鎖時,便應立即把它們從死鎖狀態中解脫出來。常採用解除死鎖的兩種方法是: 
(1) 剝奪資源。從其它進程剝奪足夠數量的資源給死鎖進程,以解除死鎖狀態。 
(2) 撤消進程。最簡單的撤消進程的方法是使全部死鎖進程都夭折掉;稍微溫和一點的方法是按照某種順序逐個地撤消進程,直至有足夠的資源可用,使死鎖狀態消除爲止。

 

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