一. 死鎖的概念
兩個或多個程式,由於資源的競爭或者彼此間的通訊而造成的阻塞現象,如果沒有外力干預,它們將無法進行下去,這就發生了死鎖。
更規範的定義:集合中的每一個程式都在等待只能由本集合中的其他程式才能引發的事件(資源),那麼該組程式是死鎖的。
上面說到的競爭的資源可以是一切能稱為資源的東西,比如鎖,網路連線,通知事件,磁碟等
舉個例子
比方說有兩個執行緒,a 和 b
- a 執行緒持有鎖 a,等待鎖 b
- b 執行緒持有鎖 b,等待鎖 a
這樣這兩個執行緒就出現了死鎖
二. 產生死鎖的必要條件
- 互斥條件:一個資源一次只能被一個程式訪問;一旦分配給某個程式,其他程式就不能再訪問(因為競爭資源的被佔用是發生死鎖的一個重要原因)
- 請求和保持條件:程式在等待其他執行緒佔用的資源(請求),與此同時,程式會一直佔用著自己已經獲得的資源(保持)
- 不可剝奪條件:程式對於已經申請到的資源在使用完成之前不可以被剝奪(也就是不會被其他程式搶走自己的資源)
- 環路等待條件:發生死鎖的程式組中,每一個程式都會佔有另一個程式所需要的資源,這個佔有關係可以形成一個等待環路(也就是競爭資源被相互佔用,無法找到突破口,只能被死死堵著)
三. 如何預防和避免死鎖
1. 以特定的順序獲取資源
以上面的例子來說,A 執行緒先嚐試獲取 a 鎖再嘗試獲取 b 鎖;而 B 執行緒則相反。這樣子的設計就很可能出現死鎖。
如果一組執行緒都按照同樣的順序來嘗試獲得鎖,那麼就可以避免死鎖的發生了。
2. 超時放棄
設定佔有資源或者佔有鎖的最大時間,如果超過該時間仍未釋放,那麼就主動釋放該資源
3. 預先分配資源
在程式執行之前一次性的將其所需(申請)的資源分配給他,保證其可以正常的執行完畢,不需要在程式執行時再申請資源。
該方法的弊端體現在資源利用率低的方面,因為並不是所有的資源在程式執行時都會被長時間佔用,但是這樣在執行前一次性分配的方式會造成所有資源在該程式的生命週期內都是不可以被釋放的。
4. 銀行家演算法
銀行家演算法是一種直接避免死鎖的方式,主要的思想就是動態檢查程式對資源的申請,以檢查是否會造成死鎖。
我們通過列出各個執行緒(程式)的當前佔有的資源,最大所需要的資源以及當前剩餘的可利用資源,可以計算可得知當前是否為安全狀態(也就是一定不會發生死鎖),簡單來說就是確保當前可以利用的資源是充足的
四. 檢測死鎖
如果我們不去主動預防或者避免死鎖,那麼我們可以通過及時檢測當前是否出現死鎖的方式來處理死鎖問題,比如使用一些死鎖檢測演算法
而什麼時候去進行死鎖檢測又取決於死鎖發生的頻率以及一般情況下死鎖涉及的程式數;這樣,我們既可以選擇定時檢測,也可以在發現資源利用率下降時進行檢測,總之,你得知道發生死鎖時,你的程式會有怎樣的變化。
監控工具 JConsole
JDK 也自帶了一個監控工具 JConsole,在JDK/bin目錄下可以找到。
連線本地程式:
查詢是否存在死鎖:
五. 解除死鎖
當發生了死鎖之後,我們肯定要去解決掉它的。最直接的方法肯定是重啟,當然很多時候這樣做不太可取。
- 終止相關程式:檢測出與死鎖有關的程式,撤銷或者掛起它,強制它釋放資源
- 剝奪資源:將部分被死鎖程式佔用的資源剝奪出來,解除死鎖
- 程式回退:將死鎖程式回退到未出問題之前,不過實現難度較大。