A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

18.什么情况下java程序会产生死锁?如何定位、修复?

    死锁是一种特定的程序状态,在实体之间,由于循环依赖导致彼此一直处于等待之中,没有任何个体可以继续前进。死锁不仅仅是在线程之间会发生,存在资源独占的进程之间同样也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。

    定位死锁最常用的工具就是利用jstack等工具获取线程栈,然后定位相互之间的依赖关系,进而找到死锁。如果是比较明显的死锁,往往jstack工具就能直接定位,类似JConsole甚至可以在图形界面进行有限的死锁检测。

    如果程序运行时发生了死锁,绝大多数情况下都是无法在线解决的,只能重启、修正程序本身问题。所以,代码开发阶段相互审查,或者利用工具进行预防性排查,也是很重要的。


写一个基本的死锁程序:


  • public class DeadLock extends Thread{



  •         private String first;



  •         private String second;



  •         public DeadLock(String name,String first,String second){



  •                 super(name);



  •                 this.first=first;



  •                 this.second=second;



  •         }



  •         public void run(){



  •                 synchronized(first){



  •                         System.out.println(this.getName()+" obtained:"+first);



  •                         try{



  •                                 Thread.sleep(1000L);



  •                                 synchronized (second) {



  •                                         System.out.println(this.getName()+" obtained:"+second);



  •                                 }



  •                         }catch(InterruptedException e){



  •                                 //Do nothing



  •                         }



  •                 }



  •         }







  •         public static void main(String[] args) throws InterruptedException{



  •                 String lockA="lockA";



  •                 String lockB="lockB";



  •                 DeadLock t1=new DeadLock("Thread1", lockA, lockB);



  •                 DeadLock t2=new DeadLock("Thread2", lockB, lockA);



  •                 t1.start();



  •                 t2.start();



  •                 t1.join(); //调用join函数的线程执行完毕主线程才会继续运行



  •                 t2.join();



  •         }







  • }


    这个程序编译执行后,几乎每次都可以重现死锁。



  • Thread2 obtained:lockB



  • Thread1 obtained:lockA


为什么先调用t1.start(),但是t2却先打印出来了。因为线程调度依赖于操作系统调度器,虽然可以通过优先级之类的进行影响,但是具体情况是不确定的。

    下面模拟问题定位,jstack。

首先使用jps或者系统的ps命令、任务管理器等工具,确定进程ID:8508。

    如上图所示,找到处于BLOCKED状态的线程,按照试图获取(WAITING)的锁ID查找,很快就定位问题。jstack本身也会把类似的简单死锁抽取出来,直接打印出来。

    在实际应用中,类死锁情况未必有如此清晰的输出,但是总体上可以理解为:区分线程状态-查看等待目标-对比Monitor等持有状态。


如何在编程中尽量预防死锁?

    死锁的发生基本上是因为:

(1)互斥条件,类似java中Monitor都是独占的,要么是我用,要么是你用。

(2)互斥条件是长期持有的,在使用结束之前,自己不会释放,也不能被其他线程抢占。

(3)循环依赖关系。两个或多个个体之间出现了锁的链条环。

据此分析可能的避免死锁的思路和方法:

(1)尽量避免使用多个锁,并且只有需要时才持有锁;

(2)如果必须使用多个锁,尽量设计好锁的获取顺序;

(3)使用带超时的方法,为程序带来更多可控性。


有时候并不是阻塞导致的死锁,只是某个线程进入了死循环,导致其他线程一直等待,这种问题如何诊断?

    死锁的另一个好朋友就是饥饿。死锁和饥饿都是线程活跃性问题。实践中死锁可以使用JVM自带的工具进行排查。

    死循环死锁可以认为是自旋锁死锁的一种,其他线程因为等待不到具体的信号提示,导致线程一直饥饿。这种情况下可以查看线程CPU使用情况,排查出使用CPU时间片最高的线程,再打出该线程的堆栈信息,排查代码。

    基于互斥量的锁如果发生死锁往往CPU使用率较低,实践中也可以从这一方面进行排查。


5 个回复

倒序浏览
优秀,奈斯
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马