【工作隨手記】deaklock排查

生產環境當中還沒真正遇到過死鎖的問題。有些疑似死鎖的問題,後來經過排查也只是其它問題導致的。所以通過jstack到底怎樣排查死鎖問題有點疏忽了。這裏作個記錄。

模擬一個死鎖

順便複習一下。

死鎖的產生有四個必要的條件

互斥使用,即當資源被一個線程佔用時,別的線程不能使用
不可搶佔,資源請求者不能強制從資源佔有者手中搶奪資源,資源只能由佔有者主動釋放
請求和保持,當資源請求者在請求其他資源的同時保持對原因資源的佔有
循環等待,多個線程存在環路的鎖依賴關係而永遠等待下去,例如T1佔有T2的資源,T2佔有T3的資源,T3佔有T1的資源,這種情況可能會形成一個等待環路
對於死鎖產生的四個條件只要能破壞其中一條即可讓死鎖消失,但是條件一是基礎,不能被破壞。

模擬一個死鎖。

 private static String lock1 = "lock1";
    private static String lock2 = "lock2";

    public static void main(String[] args) {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                synchronized (lock1){
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + " 鎖住了lock1");
                    synchronized (lock2){
                        System.out.println(Thread.currentThread() + " 鎖住了lock2");
                    }
                }
            }
        };

        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                synchronized (lock2){
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + " 鎖住了lock2");
                    synchronized (lock1){
                        System.out.println(Thread.currentThread() + " 鎖住了lock1");
                    }
                }
            }
        };

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.submit(r1);
        executorService.submit(r2);

    }

執行輸出

Thread[pool-1-thread-1,5,main] 鎖住了lock1
Thread[pool-1-thread-2,5,main] 鎖住了lock2

後面一直卡住,通過idea查看堆棧信息可以看到,兩個線程互相一直在等待對方釋放鎖。

"pool-1-thread-2" #13 prio=5 os_prio=0 tid=0x000000001ebc6000 nid=0xcf950 waiting for monitor entry [0x00000000207fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.nyp.test.DeadlockTest$2.run(DeadlockTest.java:49)

  • waiting to lock <0x000000076b19de70> (a java.lang.String)
  • locked <0x000000076b19dea8> (a java.lang.String)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

"pool-1-thread-1" #12 prio=5 os_prio=0 tid=0x000000001ebc5000 nid=0xcfa20 waiting for monitor entry [0x00000000206ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.nyp.test.DeadlockTest$1.run(DeadlockTest.java:31)

  • waiting to lock <0x000000076b19dea8> (a java.lang.String)
  • locked <0x000000076b19de70> (a java.lang.String)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

通過idea我們很方便的觀察到了兩個線程在等待對方釋放鎖,而且通過觀察其它的堆棧信息我們也能方便的知道,兩個線程也分別鎖住了對方想要申請的鎖,因此造成了死鎖。

但是在生產環境中,通過jstack會打印出一大堆線程的信息,而且只有有併發環境必然會上鎖,堆棧信息當中必然會出現waiting for monitor``waiting on condition``locked等信息,這並不是死鎖的完全充要條件。

將代碼放到生產環境。通過jstack pid命令,可以看到會出現明確的deadlock的信息。

Found one Java-level deadlock:

"pool-4-thread-2":
waiting to lock monitor 0x00007f0c24026408 (object 0x00000005d0e7a708, a java.lang.String),
which is held by "pool-4-thread-1"
"pool-4-thread-1":
waiting to lock monitor 0x00007f0c24025c78 (object 0x00000005d0e7a740, a java.lang.String),
which is held by "pool-4-thread-2"

Java stack information for the threads listed above:

"pool-4-thread-2":
at com.alpha.data.util.DeadlockTest$2.run(DeadlockTest.java:49)

  • waiting to lock <0x00000005d0e7a708> (a java.lang.String)
  • locked <0x00000005d0e7a740> (a java.lang.String)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:750)
    "pool-4-thread-1":
    at com.alpha.data.util.DeadlockTest$1.run(DeadlockTest.java:31)
  • waiting to lock <0x00000005d0e7a740> (a java.lang.String)
  • locked <0x00000005d0e7a708> (a java.lang.String)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:750)

Found 1 deadlock.

也就是在生產環境中,通過jstack排查死鎖問題時 ,只需要盯着deadlock字樣即可,如果有死鎖會明顯的提示出產生死鎖的代碼所在。否則,便是沒有死鎖。

順便複習一下通過jstack排查cpu佔用高的問題

1.通過top命令找到cpu佔用高的應用程序進程
2.通過top -H -p pid查看該應用中佔用CPU高的線程。
3.通過printf "%x\n" pid 將線程高的線程號轉爲十六進制。
4.通過jstack過濾該十六進制的關鍵信息。jstack pid | grep 十六進制 -c 10

這樣就可以看到佔用CPU高的代碼位置。

總結:就是先查到佔用高的應用和具體的線程,然後根據線程到堆積信息查找即可。
不過堆棧信息非十進制,需提前把線程號轉爲十六進制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章