【工作随手记】deaklock排查

2023-07-29,,

生产环境当中还没真正遇到过死锁的问题。有些疑似死锁的问题,后来经过排查也只是其它问题导致的。所以通过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高的代码位置。

总结:就是先查到占用高的应用和具体的线程,然后根据线程到堆积信息查找即可。

不过堆栈信息非十进制,需提前把线程号转为十六进制。

工作随手记】deaklock排查的相关教程结束。

《【工作随手记】deaklock排查.doc》

下载本文的Word格式文档,以方便收藏与打印。