並發編程之死鎖
- 2019 年 10 月 8 日
- 筆記
產生死鎖的4個必要條件
-
互斥條件:在一段時間內某資源僅為一個執行緒所佔有
-
不可剝奪條件:執行緒所獲得的資源在未使用完畢之前,不能被其他執行緒強行奪走
-
請求和保持條件:執行緒已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他執行緒佔有
-
循環等待條件:存在一種執行緒資源的循環等待鏈,鏈中每一個執行緒已獲得的資源同時被鏈中下一個執行緒所請求。
產生死鎖的情況
-
多個鎖的交叉(交叉鎖)
交叉鎖引起的執行緒會進入BLOCKED狀態,CPU資源棧用不高,很容易藉助工具發現
情景描述:執行緒A持有鎖1,等待獲取鎖2;執行緒B持有鎖2,等待獲取鎖1。
private final static Object MUTEX_READ = new Object(); private final static Object MUTEX_WRITE = new Object(); public void read(){ synchronized (MUTEX_READ) { synchronized (MUTEX_WRITE) { } } } public void write(){ synchronized (MUTEX_WRITE) { synchronized (MUTEX_READ) { } } }
-
記憶體不足
-
一問一答式的數據交換
伺服器開啟了某個埠,等待客戶端的訪問。客戶端發送請求後等待伺服器的響應,伺服器因為某種原因錯過了客戶端的請求,讓在等待請求。此時,服務端可客戶端都在等待對方發送數據。
-
資料庫鎖
比如某個執行緒執行了for update語句後退出了事務,其他執行緒訪問的時候就會陷入死鎖
-
文件鎖
獲取文件鎖的執行緒意外退出,其它執行緒無法獲取該文件鎖會進入死鎖。
-
死循環引起的死鎖【系統假死,查看堆棧資訊無法發現任何死鎖跡象,CPU佔有率居高不下,是最為致命以及最難排查的死鎖現象】
處理死鎖的方法
通過設置某些限制條件,去破壞產生死鎖的四個必要條件中的一個或幾個條件,來防止死鎖的發生。【在死鎖產生的四個必要條件中,“互斥條件”是無法破壞的,破壞“互斥條件”會造成結果的不可再現性】
-
破壞“不可剝奪條件”
允許對資源實行搶奪。
方法一:如果佔有某些資源的一個執行緒進行進一步資源請求被拒絕,則該執行緒必須釋放它最初佔有的資源【如果有需要,可再次請求這些資源和另外的資源】。
方法二:允許優先順序高的執行緒搶佔優先順序低的執行緒的資源。
-
破壞“請求和保持條件”
在系統中不允許進程在已獲得某種資源的情況下,申請其他資源。
方法一:採用“ 一次性分配”方案,即:創建進程時,要求它申請所需的全部資源,系統或滿足其所有要求,或什麼也不給它。
方法二:要求每個進程提出新的資源申請前,釋放它所佔有的資源。
-
破壞“循環等待條件”
將系統中的所有資源進行編號,執行緒必須按照順序申請資源。
避免死鎖的處理方式
-
加鎖順序:給所有的鎖排序,執行緒只能按照升序(降序)的獲取鎖。【破壞循環等待條件】
-
加鎖超時:給執行緒設置獲取鎖的最大等待時間,如果超時則放棄對該鎖的請求並釋放已佔有的鎖,等待一段時間後再次請求。【破壞請求和保持條件】
-
死鎖檢測
主要是針對那些不可能實現按序加鎖並且鎖超時也不可行的場景。每當一個執行緒請求或者獲得了鎖,會在執行緒和鎖相關的數據結構中將其記下。
執行緒A等待執行緒B,執行緒B等待執行緒C,執行緒C等待執行緒D,執行緒D又在等待執行緒A。執行緒A為了檢測死鎖,它需要遞進地檢測所有被B請求的鎖。從執行緒B所請求的鎖開始,執行緒A找到了執行緒C,然後又找到了執行緒D,發現執行緒D請求的鎖被執行緒A自己持有著。這是它就知道發生了死鎖。
檢測和解除死鎖
由於作業系統有並發、共享以及隨機性等特點,通過預防和避免的手段達到排除死鎖的目的是很困難的。一種簡便的方法是系統為進程分配資源時,不採取任何限制性措施,但是提供了檢測和解脫死鎖的手段:能發現死鎖並從死鎖狀態中恢復出來。因此,在實際的作業系統中往往採用死鎖的檢測和解除方法來排除死鎖。
死鎖檢測和解除是指系統設有專門的機制,當死鎖發生時,該機制能夠檢測到死鎖發生的位置和原因,並能通過外力破壞死鎖發生的必要條件,從而使得並發進程從死鎖狀態中恢復出來。
-
解除死鎖
1) 資源剝奪法:掛起某些死鎖進程,並釋放它的資源,將這些資源分配給其他的死鎖進程。【但應防止被掛起的進程長時間得不到資源,而處於資源匱乏的狀態】
2) 撤銷執行緒法:強制撤銷部分、甚至全部死鎖執行緒。【撤銷的原則可以按進程優先順序和撤銷進程代價的高低進行】
3) 進程回退法:讓一(多)個執行緒回退到足以迴避死鎖的地步【要求系統保持進程的歷史資訊,設置還原點】