当前位置: 首页 > 科技观察

如何排查Java应用程序死锁

时间:2023-03-16 13:52:11 科技观察

首先,我们构建一个死锁场景。如何构造死锁?很简单,就是让线程1占有对象a的锁,然后去申请对象b的锁。同时,对象2已经占有了对象b的锁,然后请求对象a的锁。线程1和线程2互相等待,形成死锁。(在面试中,经常需要手写死锁)代码如下:RequestMapping("/test")publicStringtestDeadLock(){finalObjecta=newObject();finalObjectb=newObject();newThread(()->{synchronized(a){System.out.println(Thread.currentThread().getName()+"占用对象a的锁");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"请求对象b的锁");synchronized(b){System.out.println(Thread.currentThread().getName()+"占用对象b的锁");}}},"Thread1")。start();newThread(()->{synchronized(b){System.out.println(Thread.currentThread().getName()+"持有对象b的锁");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"请求对象a的锁");同步d(a){System.out.println(Thread.currentThread().getName()+"占用了对象a的锁");}}},"Thread2").start();return"success";}}输出结果如下:如果没有干预,两个线程会一直死锁。这只是一个简单的死锁场景。如果线上出现这种情况,如何定位呢?我们把死锁代码放到一个简单的SpringBoot中用mvninstall打包后,把完成的jar包放到我们的实验机中。我的项目名称叫做dl。使用nohupjava-jardl-0.0.1-SNAPSHOT.jar&部署运行使用curlhttp:port/test调用接口。如果不出意外,该程序现在陷入僵局。首先获取java应用的进程,ps-ef|grepDL|grep-vgrep,得到pid为12156,然后打印出该进程下线程的状态,输出到dl.txt,jstack12156>dl.txtjstack可以用来生成当前线程的快照虚拟机快速定位多线程使用不当导致的问题。在该txt文件的末尾,我们可以看到下面的内容:FoundoneJava-leveldeadlock:====================================================================":waitingtolockmonitor0x00007f9ea8006008(object0x00000000e367d550,ajava.lang.Object),whichisheldby"Thread1""Thread1":waitingtolockmonitor0x00007f9ea8003f08(object0x00000000e367d560,ajava.lang.Object),whichisheldby"Thread2"Javastackinformationforthethreadslistedabove:==================================================="Thread2":atcom.example.dl.Controller.lambda$testDeadLock$1(Controller.java:40)-waitingtolock<0x00000000e367d550>(ajava.lang.Object)-locked<0x00000000e367d560>(ajava.lang.Object)atcom.example.dl.Controller$$Lambda$469/1627217364。运行(UnknownSource)atjava.lang.Thread.run(Thread.java:748)“Thread1”:atcom.example.dl.Controller.lambda$testDeadLock$0(Controller.java:25)-waitingtolock<0x00000000e367d560>(ajava.lang.Object)-locked<0x00000000e367d550>(ajava.lang.Object)atcom.example.dl.Controller$$Lambda$468/117875601.run(UnknownSource)atjava.lang.Thread.run(Thread.java:748)发现1个死锁。可以很明显的看到Thread2申请了Thread1占用的锁,Thread1申请了Thread2占用的锁,这就构成了死锁。当然这个场景很简单,但是线上环境复杂。当界面响应变慢,CPU负载越来越高时,可以使用jstack命令查看java进程中线程的状态,看是否存在死循环、死锁等,然后根据具体情况进行分析情况。比如要按顺序获取对象的锁,只能按照从a到b的顺序。线程1获得对象a的锁后,在试图获得对象b的锁时,线程2想直接获取。对于对象b的锁,必须先获得对象a的锁,这样才会被线程1阻塞,待线程1运行完毕,释放所有锁后,线程2才能继续运行。或者超时就直接放弃,把synchronized改成ReentranLock,用它的tryLock方法带时间,如果一定时间内拿不到锁,直接放弃对锁的申请。