前言
相信程序員都會碰上這樣的問題,Java死鎖如何排查?又如何解決呢?那麼,何為死鎖呢?死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象。今天一次性來幫助大家解決Java死鎖的有關問題。
實例
死鎖的本質,舉個例子如果此時有一個線程 A ,按照先獲持有鎖 a 再獲取鎖 b的順序獲得鎖,同時另外一個線程 B,按照先獲取鎖 b 再獲取鎖 a 的順序獲取鎖。如下圖所示:
接著我們用代碼模擬上線的執行過程
直接運行,發現主線程一直處於執行中,一直無法結束
通過jdk工具jps、jstack排查死鎖問題
步驟一:使用jsp查找程序進行
jps:jdk提供的一個工具,可以查看到正在運行的java進程
步驟二:使用jstack查看線程堆棧信息
jstack:jdk提供的一個工具,可以查看java進程中線程堆棧信息。更詳細的用法見文檔最後。
<code>$ jstack 96521複製代碼/<code>
從上面的堆棧信息中我們可以發現這個內容:“Found one Java-level deadlock”,表示程序中發現了一個死鎖,後面包含更多詳細的信息,重點下面:
死鎖的代碼是在DeadLock.java的32行和18行,此時我們就可以去優化代碼,解決死鎖問題。
通過jdk提供的工具jconsole排查死鎖問題
jconsole:jdk提供的一個可視化的工具,方便排查程序的一些問題,如:程序內存溢出、死鎖問題等等。更詳細的用法見文檔最後。jconsole位於jdk的bin目錄中
<code>$ jconsole複製代碼/<code>
可以看到我們的程序,點擊連接。
在jconsole窗口中查看線程堆棧信息
點擊“檢測死鎖”,可以看到程序死鎖信息
上圖中可以看到詳細的死鎖信息,和jstack中信息類似。
通過jdk提供的工具VisualVM排查死鎖問題
VisualVM:jdk提供的一個非常強大的排查java程序問題的一個工具,可以監控程序的性能、查看jvm配置信息、堆快照、線程堆棧信息。算是程序優化的必備工具。工具位於jdk的bin目錄中。
<code>$ jvisualvm複製代碼/<code>
切換到“線程”窗口
執行提示有死鎖情況。在線程窗口中點擊“線程Dump”按鈕。
查看堆棧信息
線程堆棧快照的信息和jstack查看到的信息一樣,即可發現死鎖代碼。
如何避免死鎖?
我們知道了死鎖如何產生的 ,那麼就知道該如何去預防。如果一個線程每次只能獲取一個鎖,那麼就不會出現由於嵌套持有鎖順序導致的死鎖。
1. 正確的順序獲得鎖
如果必須獲取多個鎖,我們就要考慮不同線程獲取鎖的順序。
上面的例子出現死鎖的根本原因就是獲取所的順序是亂序的,超乎我們控制的。上面例子最理想的情況就是把業務邏輯抽離出來,把獲取鎖的代碼放在一個公共的方法裡面,讓這兩個線程獲取鎖都是從我的公共的方法裡面獲取。
當Thread1線程進入公共方法時,獲取了A鎖,另外Thread2又進來了,但是A鎖已經被Thread1線程獲取了,所以只能阻塞等待。Thread1接著又獲取鎖B,Thread2線程就不能再獲取不到了鎖A,更別說再去獲取鎖B了,這樣就有一定的順序了。只有當線程1釋放了所有鎖,線程B才能獲取。
比如前面的例子我們改成
查看打印結果,我們發現 線程0 獲取成功然後線程1才能繼續獲取
2. 超時放棄
當線程獲取鎖超時了則放棄,這樣就避免了出現死鎖獲取的情況。當使用synchronized關鍵詞提供的內置鎖時,只要線程沒有獲得鎖,那麼就會永遠等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,該方法可以按照固定時長等待鎖,
因此線程可以在獲取鎖超時以後,主動釋放之前已經獲得的所有的鎖。通過這種方式,也可以很有效地避免死鎖。總結
死鎖就是“兩個任務以不合理的順序互相爭奪資源”造成,因此為了規避死鎖,應用程序需要妥善處理資源獲取的順序。另外有些時候,死鎖並不會馬上在應用程序中體現出來,在通常情況下,都是應用在生產環境運行了一段時間後,才開始慢慢顯現出來,在實際測試過程中,由於死鎖的隱蔽性,很難在測試過程中及時發現死鎖的存在,而且在生產環境中,應用出現了死鎖,往往都是在應用狀況最糟糕的時候——在高負載情況下。因此,開發者在開發過程中要謹慎分析每個系統資源的使用情況,合理規避死鎖。
閱讀更多 Java技術前沿 的文章