JavaOne年度會議的一大優點是,主題專家介紹了幾個技術和故障排除實驗室。 其中的一個實驗室今年特別吸引了我的注意力:“ HOL6500-查找和解決Java死鎖 ”,由Java冠軍Heinz Kabutz提出 。 這是我在該主題上看到的最好的演示之一。 我建議您自己下載,運行和研究實驗室。
本文將重溫這個經典的線程問題,并總結提出的關鍵故障排除和解決方法。 我還將根據自己的多線程故障排除經驗來擴展主題。
Java死鎖:這是什么?
真正的Java死鎖本質上可以描述為兩個或多個線程永遠被阻塞,互相等待的情況。 這種情況與其他更常見的“日常”線程問題模式(例如鎖爭用和線程爭用,等待阻塞IO調用的線程等)截然不同。這種鎖排序死鎖情況可以如下所示:
在上面的可視示例中,線程A和線程B嘗試以不同順序獲取2個鎖是致命的。 一旦線程達到死鎖狀態,它們將永遠無法恢復,從而迫使您重新啟動受影響的JVM進程。
Heinz還描述了另一種死鎖: 資源死鎖 。 到目前為止,這是我在Java EE企業系統故障排除經驗中最常見的線程問題模式。 資源死鎖本質上是一種場景,其中一個或多個線程正在等待獲取永遠無法使用的資源,例如JDBC池耗盡。
鎖排序死鎖
您現在應該知道我是JVM線程轉儲分析的忠實擁護者 ; 對于參與Java / Java EE開發或生產支持的個人而言至關重要的技能。 好消息是,大多數JVM線程轉儲格式(HotSpot,IBM VM…)都可以很容易地對Java級別的死鎖進行開箱即用的識別,因為它們包含本機死鎖檢測機制,該機制實際上將向您顯示所涉及的線程。真正的Java級死鎖場景以及執行堆棧跟蹤。 可以通過您選擇的工具(例如JVisualVM,jstack或本機例如基于Unix的操作系統上的kill -3 <PID>)捕獲JVM線程轉儲。 在運行實驗1之后,在JVM Java級死鎖檢測部分下面找到:
現在,這是簡單的部分……根本原因分析工作的核心是首先了解為什么此類線程涉及死鎖情況。 鎖順序死鎖可能會從您的應用程序代碼中觸發,但是除非您參與高并發性編程,否則,可能的罪魁禍首是您正在使用的第三方API或框架或實際的Java EE容器本身(如果適用)。
現在讓我們在下面回顧一下亨氏提出的鎖排序死鎖解決策略:
#通過全局排序解決死鎖(請參閱lab1解決方案)
- 本質上涉及對鎖的全局排序的定義,它將始終防止死鎖(請參閱lab1解決方案)
#TryLock解決死鎖(請參閱lab2解決方案)
- 鎖定第一把鎖
- 然后嘗試鎖定第二把鎖
- 如果您可以鎖定它,那就很好了
- 如果不能,請再試一次
可以使用Java Lock&ReantrantLock來實現上述策略,這也使您能夠靈活地設置等待超時,以防止在第一個鎖獲取時間太長的情況下導致線程不足。
public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException;void unlock();Condition newCondition();}
如果查看JBoss AS7實現,您會注意到Lock&ReantrantLock在核心實現層中得到了廣泛使用,例如:
- 部署服務
- EJB3實現(廣泛使用)
- 集群和會話管理
- 內部緩存和數據結構(LRU,ConcurrentReferenceHashMap…)
現在,按照亨氏的觀點,死鎖解決方案#2可能非常有效,但也需要采取適當的措施,例如通過finally {}塊釋放所有持有的鎖,否則您可以將死鎖方案轉換為活動鎖 。
資源僵局
現在,讓我們轉到資源死鎖場景。 我很高興Heinz的實驗室#3涵蓋了這一點,因為根據我的經驗,這是迄今為止您將看到的最常見的“死鎖”場景,尤其是在開發和支持大型分布式Java EE生產系統時。
現在,讓我們弄清事實。
- 資源死鎖不是真正的Java級死鎖
- 如果您遇到這些類型的死鎖,那么JVM線程轉儲將不會神奇。 這意味著您需要做更多工作來分析和理解此問題作為起點。
- 當您剛開始學習如何讀取線程轉儲時,線程轉儲分析可能會特別令人困惑,因為對于Java級死鎖,線程通常會顯示為RUNNING狀態還是BLOCKED狀態。 現在,重要的是要記住線程狀態對于這種類型的問題不是那么重要,例如RUNNING state!= Healthy state。
- 分析方法與Java級別的死鎖非常不同。 您必須創建多個線程轉儲快照,并確定每個快照之間的線程問題/等待模式。 您將能夠看到線程沒有移動,例如等待從池中獲取資源的線程以及已經獲取該資源并掛起的其他線程。
- 線程轉儲分析不是這里重要的唯一數據點/事實。 您將需要收集其他事實,例如有關線程正在等待的資源,整體中間件或環境運行狀況等的統計信息。所有這些事實的結合將使您能夠得出根本原因以及解決策略的結論,或可能不涉及代碼更改。
我將以更多的線程轉儲問題模式與您聯系,但首先請確保您對JVM線程轉儲的基本原理感到滿意。
結論
我希望您像我一樣有機會回顧,運行和享受亨氏演講中的實驗室。 并發編程和故障排除可能會非常具有挑戰性,但是我仍然建議您花一些時間來理解其中一些原則,因為我相信您會在不久的將來遇到某種情況,這將迫使您進行這種深入研究并掌握這些原則。技能。
參考: Java EE支持模式和Java教程博客中的JCG合作伙伴 Pierre-Hugues Charbonneau提供的Java死鎖故障排除和解決方法 。
翻譯自: https://www.javacodegeeks.com/2012/11/java-deadlock-troubleshooting-and-resolution.html