Java--死鎖以及死鎖的排查

最近遇到了死鎖的問題,所以這里分析并總結(jié)下死鎖,給出一套排查解決方案。

死鎖示例一

清單一

 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() {
      // 獲取臨界區(qū)A
      synchronized (lockA) {
        System.out.println("get lockA success");
        // 模擬耗時操作
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        // 獲取臨界區(qū)B
        synchronized (lockB) {
          System.out.println("get lockB success");
        }
      }
    }
  }

  /**
   * ThreadB先獲取lockB,在獲取lockA
   */
  private static class ThreadB extends java.lang.Thread {

    @Override
    public void run() {
      // 獲取臨界區(qū)A
      synchronized (lockB) {
        System.out.println("get lockB success");
        // 模擬耗時操作
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        // 獲取臨界區(qū)B
        synchronized (lockA) {
          System.out.println("get lockA success");
        }
      }
    }
  }
}

清單一代碼有點長,但是邏輯很簡單,有兩個臨界區(qū)變量lockAlockB,線程A先獲取到lockA在獲取lockB,線程B則與之相反順序獲取鎖,那么就可能會有以下情況:
線程A獲取到lockA之后發(fā)現(xiàn)lockB已被線程B獲取,那么此時線程A進入blocked狀態(tài)。同理線程B獲取lockA時發(fā)現(xiàn)其被線程A獲取,那么線程B也進入blocked狀態(tài),那么這就是死鎖。

可以總結(jié)下,這種類型的死鎖源于鎖的嵌套,由于線程與線程之間的互相看對方都是亂序執(zhí)行,因此加鎖的順序和釋放順序都是難以保證的,鎖的互相嵌套在多線程下是一個很危險的操作,因此需要額外注意。

可以總結(jié)下,這種類型的死鎖源于鎖的嵌套,由于線程與線程之間的互相看對方都是亂序執(zhí)行,因此加鎖的順序和釋放順序都是難以保證的,鎖的互相嵌套在多線程下是一個很危險的操作,因此需要額外注意。

死鎖示例二

清單二

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的代碼來自并發(fā)編程網(wǎng)-死鎖,下方代碼可以理解為一個組合模式,那么在多線程的環(huán)境下如果線程1調(diào)用parent.addChild(child)方法的同時有另外一個線程2調(diào)用child.setParent(parent)方法,兩個線程中的parent表示的是同一個對象,child亦然,此時就會發(fā)生死鎖。下面的偽代碼說明了這個過程:

Thread 1: parent.addChild(child); //locks parent
          --> child.setParentOnly(parent);

Thread 2: child.setParent(parent); //locks child
          --> parent.addChildOnly()

也可以總結(jié)下:這種類型的死鎖本質(zhì)原因也是鎖的嵌套問題,child.setParent(parent)該方法執(zhí)行首先需要獲取到child這個對象鎖,然后其內(nèi)部調(diào)用parent的方法則需要獲取parent的對象鎖,那么就形成了鎖嵌套,因此會出現(xiàn)死鎖。

死鎖示例三

清單三是一種開發(fā)人員經(jīng)常犯的錯誤,一般都是由于某些中斷操作沒有釋放掉鎖,所以也叫(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

jstackjcmd是JDK自帶的工具包,使用jstack -l pid或者jcmd pid Thread.print可以查看當前應用的進程信息,如果有死鎖也會分析出來。比如清單一中的死鎖會分析出以下結(jié)果:

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.

在分析中明確指出發(fā)現(xiàn)了死鎖,是由于Thread-1Thread-0鎖的互斥導致的死鎖。

有時候文件分析不是很容易看,此時可以借助一些工具來分析,比如
http://gceasy.io/,其分析整理后使得結(jié)果更加容易看到。

image

資源死鎖排查

由于資源沒釋放的死鎖使用jstack等手段難以排查,這種棘手的問題一般要多次dump線程快照,參考kabutz/DeadlockLabJavaOne2012給出的經(jīng)驗主要有以下兩種方式排查:
能夠控制資源死鎖的情況:

  1. 在死鎖前dump出線程快照
  2. 在死鎖后再次dump出線程快照
  3. 兩者比較

已經(jīng)死鎖

  1. 每隔一段時間dump出線程快照
  2. 對比找到不會改變的那些線程再排查問題

應用自行檢查

在Java中提供了ThreadMXBean類可以幫助開發(fā)者查找死鎖,該查找效果與jstack一致,對于資源釋放不當死鎖是無法排查的。
使用方法如清單4所示,要注意的是死鎖的排查不是一個很高效的流程,要注意對應用性能的影響。

清單四

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadsIds = threadMXBean.findDeadlockedThreads();

參考

http://ifeve.com/deadlock/
https://github.com/kabutz/DeadlockLabJavaOne2012

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容