最近遇到了死鎖的問題,所以這裏分析並總結下死鎖,給出一套排查解決方案。
死鎖示例一
清單一
public class SynchronizedDeadLock { private static final Object lockA = new Object(); private static final Object lockB = new Object(); /** * ThreadA先獲取lockA,在獲取lockB */ private static class ThreadA extends java.lang.Thread { @Override public void run() { // 獲取臨界區A synchronized (lockA) { System.out.println("get lockA success"); // 模擬耗時操作 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 獲取臨界區B synchronized (lockB) { System.out.println("get lockB success"); } } } } /** * ThreadB先獲取lockB,在獲取lockA */ private static class ThreadB extends java.lang.Thread { @Override public void run() { // 獲取臨界區A synchronized (lockB) { System.out.println("get lockB success"); // 模擬耗時操作 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 獲取臨界區B synchronized (lockA) { System.out.println("get lockA success"); } } } } }
清單一
代碼有點長,但是邏輯很簡單,有兩個臨界區變量lockA
,lockB
,線程A先獲取到lockA
在獲取lockB
,線程B則與之相反順序獲取鎖,那麼就可能會有以下情況:
線程A獲取到lockA
之後發現lockB
已被線程B獲取,那麼此時線程A進入blocked狀態。同理線程B獲取lockA
時發現其被線程A獲取,那麼線程B也進入blocked狀態,那麼這就是死鎖。
可以總結下,這種類型的死鎖源於鎖的嵌套,由於線程與線程之間的互相看對方都是亂序執行,因此加鎖的順序和釋放順序都是難以保證的,鎖的互相嵌套在多線程下是一個很危險的操作,因此需要額外注意。
死鎖示例二
清單二
public class TreeNode { TreeNode parent = null; List children = new ArrayList(); public synchronized void addChild(TreeNode child){ if(!this.children.contains(child)) { this.children.add(child); child.setParentOnly(this); } } public synchronized void addChildOnly(TreeNode child){ if(!this.children.contains(child)){ this.children.add(child); } } public synchronized void setParent(TreeNode parent){ this.parent = parent; parent.addChildOnly(this); } public synchronized void setParentOnly(TreeNode parent){ this.parent = parent; } }
清單2
的代碼來自併發編程網-死鎖,下方代碼可以理解爲一個組合模式,那麼在多線程的環境下如果線程1調用parent.addChild(child)
方法的同時有另外一個線程2調用child.setParent(parent)
方法,兩個線程中的parent表示的是同一個對象,child亦然,此時就會發生死鎖。下面的僞代碼說明了這個過程:
Thread 1: parent.addChild(child); //locks parent --> child.setParentOnly(parent); Thread 2: child.setParent(parent); //locks child --> parent.addChildOnly()
也可以總結下:這種類型的死鎖本質原因也是鎖的嵌套問題,child.setParent(parent)
該方法執行首先需要獲取到child這個對象鎖,然後其內部調用parent的方法則需要獲取parent的對象鎖,那麼就形成了鎖嵌套,因此會出現死鎖。
死鎖示例三
清單三
是一種開發人員經常犯的錯誤,一般都是由於某些中斷操作沒有釋放掉鎖,所以也叫(Resource deadlock
)比如下方的當i==5直接拋出異常,導致鎖沒有釋放,所以對於資源釋放語句一定要卸載finally中。
public void hello(int i) { LOCK.lock(); System.out.println(Thread.currentThread().getName() + "--hello:"+i); // 異常拋出但是沒有釋放掉鎖 if (i == 5) { throw new IllegalArgumentException("拋出異常,模擬獲取鎖後不釋放"); } LOCK.unlock(); }
這種死鎖最可怕的地方是難以排查,使用jstack時無法分析出這一類的死鎖,你大概能得到的反饋可能線程仍然處於RUNNABLE,具體排查方法看下方的死鎖排查。
死鎖的排查
jstack or jcmd
jstack
與jcmd
是JDK自帶的工具包,使用jstack -l pid
或者jcmd pid Thread.print
可以查看當前應用的進程信息,如果有死鎖也會分析出來。比如清單一
中的死鎖會分析出以下結果:
Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00007fbea28989b8 (object 0x000000076ac710a0, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x00007fbea480a158 (object 0x000000076ac710b0, a java.lang.Object), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at cn.mrdear.custom.lock.SynchronizedDeadLock$ThreadB.run(SynchronizedDeadLock.java:72) - waiting to lock <0x000000076ac710a0> (a java.lang.Object) - locked <0x000000076ac710b0> (a java.lang.Object) "Thread-0": at cn.mrdear.custom.lock.SynchronizedDeadLock$ThreadA.run(SynchronizedDeadLock.java:48) - waiting to lock <0x000000076ac710b0> (a java.lang.Object) - locked <0x000000076ac710a0> (a java.lang.Object) Found 1 deadlock.
在分析中明確指出發現了死鎖,是由於Thread-1
與Thread-0
鎖的互斥導致的死鎖。
有時候文件分析不是很容易看,此時可以藉助一些工具來分析,比如http://gceasy.io/,其分析整理後使得結果更加容易看到。
資源死鎖排查
由於資源沒釋放的死鎖使用jstack等手段難以排查,這種棘手的問題一般要多次dump線程快照,參考kabutz/DeadlockLabJavaOne2012給出的經驗主要有以下兩種方式排查: 能夠控制資源死鎖的情況:
- 在死鎖前dump出線程快照
- 在死鎖後再次dump出線程快照
- 兩者比較
已經死鎖
- 每隔一段時間dump出線程快照
- 對比找到不會改變的那些線程再排查問題
應用自行檢查
在Java中提供了ThreadMXBean
類可以幫助開發者查找死鎖,該查找效果與jstack一致,對於資源釋放不當死鎖是無法排查的。
使用方法如清單4
所示,要注意的是死鎖的排查不是一個很高效的流程,要注意對應用性能的影響。
清單四
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); long[] threadsIds = threadMXBean.findDeadlockedThreads();
參考
http://ifeve.com/deadlock/ https://github.com/kabutz/DeadlockLabJavaOne2012