併發王者課-黃金1:兩敗俱傷-互不相讓的線程如何導致了死鎖僵局

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎來到《","attrs":{}},{"type":"link","attrs":{"href":"https://juejin.cn/post/6967277362455150628","title":"","type":null},"content":[{"type":"text","text":"併發王者課","attrs":{}}]},{"type":"text","text":"》,本文是該系列文章中的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第11篇","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本篇文章中,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"我將爲你介紹多線程中的經典問題-死鎖,以及死鎖的產生原因、處理和方式預防措施","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、死鎖的產生","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"觀察下面這幅圖,線程1持有了A,但它需要B;而線程2持有了B,但是它需要A。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你看,問題就來了,A、B都在等待對方已經持有的資源,並且都不釋放,這就讓事情陷入了僵局,也就是產生了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"死鎖","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1a729deb6852ca5e8099543265a52e16.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在併發編程中,死鎖表示的是一種狀態。在這種狀態下,各方都在等待另一方釋放所持有的資源,但是它們之間又缺乏必要的通信機制,導致彼此存在環路依賴而永遠地等待下去。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"死鎖不僅存在於Java程序中,在諸如數據庫等其他中間件及分佈式架構中都會存在。在數據的設計中,會考慮到死鎖的監測和恢復。當數據庫中發生死鎖時,將選擇一個犧牲者並放棄對應的事務,同時釋放鎖定的資源。在它的競爭者執行結束後,應用程序可以重新運行這個事務,因爲它的競爭者此前已經完成事務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而,在JVM中,處理死鎖並沒有數據庫中那麼優雅。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當一組線程發生死鎖時,“遊戲”將到此結束,這些線程將不能再使用,而這可能會直接導致應用程序崩潰、性能降低或者部分功能停止","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,和其他併發問題一樣,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"死鎖是危險的,死鎖造成的影響會立即表現出來,而如果在高負載情況下,這將是一場災難","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、死鎖產生的必要條件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從第一小節的圖示中,我們可以看到死鎖產生的一些必要條件:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"互斥","attrs":{}},{"type":"text","text":":一個資源每次只能被一個線程使用。比如,上圖中的A和B同時只能被線程1和線程2其中一個使用;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"請求與保持條件","attrs":{}},{"type":"text","text":":一個線程在請求其他資源被阻塞時,對已經持有的資源保持不釋放。比如,上圖中的線程1在請求B時,並不會釋放A;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"不剝奪條件","attrs":{}},{"type":"text","text":":對於線程已經獲得的資源,在它主動釋放前,不可以主動剝奪。比如,上圖中線程1和線程2已經獲得的資源,除非自己釋放,否則不可以被強制剝奪;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"循環等待條件","attrs":{}},{"type":"text","text":":多個線程之間形成環狀等待。上圖中的線程1和線程2所形成的就是循環等待。","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、模擬並體驗死鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在瞭解什麼是死鎖及其產生的條件後,我們根據上圖中的死鎖情景,通過一段代碼來模擬體驗死鎖的發生。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據上圖所示,定義哪吒線程,在運行時將持有","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"A","attrs":{}},{"type":"text","text":"並請求","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"B","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static class NeZha implements Runnable {\n public void run() {\n synchronized(lockA) {\n System.out.println(\"哪吒: 持有A!\");\n\n try {\n Thread.sleep(10);\n } catch (InterruptedException ignored) {}\n System.out.println(\"哪吒: 等待B...\");\n\n synchronized(lockB) {\n System.out.println(\"哪吒: 已經同時持有A和B...\");\n }\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義蘭陵王線程,在運行時持有","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"B","attrs":{}},{"type":"text","text":"並請求","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"A","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static class LanLingWang implements Runnable {\n public void run() {\n synchronized(lockB) {\n System.out.println(\"蘭陵王: 持有B!\");\n\n try {\n Thread.sleep(10);\n } catch (InterruptedException ignored) {}\n System.out.println(\"蘭陵王: 等待A...\");\n\n synchronized(lockA) {\n System.out.println(\"蘭陵王: 已經同時持有A和B...\");\n }\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"啓動兩個線程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class DeadLockDemo {\n public static final Object lockA = new Object();\n public static final Object lockB = new Object();\n\n public static void main(String args[]) {\n Thread thread1 = new Thread(new NeZha());\n Thread thread2 = new Thread(new LanLingWang());\n thread1.start();\n thread2.start();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"兩個線程的輸出結果如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"哪吒: 持有A!\n蘭陵王: 持有B!\n哪吒: 等待B...\n蘭陵王: 等待A...\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從結果中可以看到,哪吒和蘭陵王分別持有了A和B,但他們又相互請求對方持有的資源,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最終導致死鎖,兩個線程進入了無限地等待","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四、死鎖的處理","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. 忽略死鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"忽略死鎖是一種鴕鳥政策,它假設永遠不會發生死鎖。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這種策略適用於死鎖發生概率較低且影響可容忍的場景,如果死鎖被證明永遠不會發生也可以採用這種策略","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. 檢測","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這種策略下,死鎖是允許發生的。如果系統檢測到死鎖,也會對其進行糾正,比如跟蹤線程狀態和資源分配。在死鎖時,可以通過一些方法進行糾正:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"線程終止","attrs":{}},{"type":"text","text":":選擇其中一個或多個線程進行終止,釋放資源,打破死鎖狀態;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"資源搶佔","attrs":{}},{"type":"text","text":":重新分配各線程已經搶佔的資源,直到打破死鎖。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3. 預防","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對待死鎖問題,預防是關鍵","attrs":{}},{"type":"text","text":"。本文第二小節已經列舉死鎖產生的一些必要條件,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"所以如果要預防死鎖,只要打破其中任一條件即可","attrs":{}},{"type":"text","text":",Java中具體的死鎖預發方式我們會在後面的文章中介紹。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就是關於死鎖的全部內容。在本文中,我們介紹了什麼是死鎖,以及死鎖產生的必要條件和應對策略。對待開發中的死鎖問題,既要保持敬畏之心,也不必聞之色變,審慎分析死鎖的可能並設計合理策略可以有效預防死鎖。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正文到此結束,恭喜你又上了一顆星✨","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"夫子的試煉","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運行本文的示例代碼,嘗試找到破解其死鎖的方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"延伸閱讀與參考資料","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Deadlock","title":"","type":null},"content":[{"type":"text","text":"死鎖","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《Java Concurrency in Practice》","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Deadlock_prevention_algorithms","title":"","type":null},"content":[{"type":"text","text":"死鎖預防算法","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.cn/post/6967277362455150628","title":"","type":null},"content":[{"type":"text","text":"《併發王者課》大綱與更新進度總覽","attrs":{}}]}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"關於作者","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號【","attrs":{}},{"type":"link","attrs":{"href":"https://writting.oss-cn-beijing.aliyuncs.com/2021/05/18/qrcodeforgh61bfe45f82b7200.jpg","title":"","type":null},"content":[{"type":"text","text":"庸人技術笑談","attrs":{}}]},{"type":"text","text":"】,獲取及時文章更新。記錄平凡人的技術故事,分享有品質(儘量)的技術文章,偶爾也聊聊生活和理想。不販賣焦慮,不做標題黨。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果本文對你有幫助,歡迎","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"點贊","attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"關注","attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"監督","attrs":{}},{"type":"text","text":",我們一起","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"從青銅到王者","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章