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

如何通过编程发现Java死锁

时间:2023-03-15 09:41:14 科技观察

死锁是指两个或多个动作一直在等待其他动作完成,使得所有动作始终处于阻塞状态。在开发阶段检测死锁非常困难,解决死锁往往需要重启程序。更糟糕的是,死锁通常发生在最重的生产过程中,在测试中不容易捕捉到。我这样说是因为测试线程之间所有可能的交集是不现实的。虽然有一些静态分析库可以帮助我们找到可能存在的死锁,但是我们有必要在运行时检测死锁并获取有用的信息,以便我们解决问题或者重启程序,或者做其他事情。在编程中使用ThreadMXBean类检测死锁Java5引入了ThreadMXBean接口,它提供了多种监控线程的方法。我建议您了解所有这些方法,因为它们为您提供了许多在您不使用外部工具时监控程序性能的有用操作。在这里,我们感兴趣的方法是findMonitorDeadlockedThreads。如果您使用的是Java6,则对应的方法是findDeadlockedThreads。两者的区别是findDeadlockedThreads还可以检测所有者锁(java.util.concurrent)引起的死锁,而findMonitorDeadlockedThreads只能检测监视器锁(例如synchronizedblocks)。由于保留旧版本的方法只是为了兼容性,我将使用新版本的方法。这里,编程思路是把死锁的周期性检测封装成一个可重用的组件,然后我们启动它就可以了。实现调度的一种方法是通过执行器框架,一组抽象良好且易于使用的多线程类。ScheduledExecutorService调度器=Executors.newScheduledThreadPool(1);this.scheduler.scheduleAtFixedRate(deadlockCheck,period,period,unit);就这么简单,我们通过选择周期和时间单位设置了具体的时间后,就得到了一个周期性调用的线程。接下来,我们要扩展功能以允许用户提供在程序检测到死锁时触发的操作。***,我们需要一个方法来接收描述死锁中所有线程的对象数组。voidhandleDeadlock(finalThreadInfodeadlockedThreads);现在,一切准备就绪,可以实现死锁检测类了。publicinterfaceDeadlockHandler{voidhandleDeadlock(finalThreadInfodeadlockedThreads);}publicclassDeadlockDetector{privatefinalDeadlockHandlerdeadlockHandler;私人最后长期;私有最终TimeUnit单元;privatefinalThreadMXBeanmbean=ManagementFactory.getThreadMXBean;privatefinalScheduledExecutorServicescheduler=Executors.newScheduledThreadPool(1);finalRunnabledeadlockCheck=newRunnable{@Overridepublicvoidrun{longdeadlockedThreadIds=DeadlockDetector.this.mbean.findDeadlockedThreads;if(deadlockedThreadIds!=null){ThreadInfothreadInfos=DeadlockDetector.this.mbean.getThreadInfo(deadlockedThreadIds);DeadlockDetector.this.deadlockHandler.handleDeadlock(threadInfos);}}};publicDeadlockDetector(finalDeadlockHandlerdeadlockHandler,finallongperiod,finalTimeUnitunit){this.deadlockHandler=deadlockHandler;this.period=period;this.unit=单位;}publicvoidstart{this.scheduler.scheduleAtFixedRate(this.deadlockCheck,this.period,this.period,this.unit);}}让我们试试看首先,我们需要创建一个处理程序来将死锁线程信息输出到System.err。在实际场景中,我们可以使用它来发送邮件,例如:");映射stackTraceMap=Thread.getAllStackTraces;for(ThreadInfothreadInfo:deadlockedThreads){if(threadInfo!=null){for(Threadthread:Thread.getAllStackTraces.keySet){if(thread.getId==threadInfo.getThreadId){System.err.println(threadInfo.toString.修剪);对于(StackTraceElementste:thread.getStackTrace){System.err.println("t"+ste.toString.trim);}}}}}}}}这个过程遍历所有堆栈跟踪并为每个线程消息打印相应的堆栈跟踪。这样我们就可以确切地知道每个线程在何处以及在等待谁。但这种方法有一个缺陷——当一个线程只是暂时等待时,可能会被认为是暂时的死锁,从而引起误报。为此,当我们处理死锁时,原线程无法继续存在,findDeadlockedThreads方法返回不存在该线程。为了避免可能出现的NullPointerException,我们需要防范这种情况。***,让我们来制造一个僵局,看看系统是如何工作的。DeadlockDetectordeadlockDetector=newDeadlockDetector(newDeadlockConsoleHandler,5,TimeUnit.SECONDS);deadlockDetector.start;finalObjectlock1=newObject;最终对象lock2=新对象;Threadthread1=newThread(newRunnable{@Overridepublicvoidrun{synchronized(lock1){System.out.println("Thread1acquiredlock1");try{TimeUnit.MILLISECONDS.sleep(500);}catch(InterruptedExceptionignore){}synchronized(lock2){System.out.println("Thread1acquiredlock2");}}}});线程1.开始;Threadthread2=newThread(newRunnable{@Overridepublicvoidrun{synchronized(lock2){System.out.println("线程2获取锁2");synchronized(lock1){System.out.println("线程2获取锁1");}}}});thread2.start;输出:Thread1acquiredlock1Thread2acquiredlock2检测到死锁!“Thread-1”Id=11在“Thread-0”拥有的java.lang.Object@68ab95e6上被阻塞;Id=10deadlock.DeadlockTester$2.run(DeadlockTester.java:42)java.lang.Thread.run(Thread.java:662)“Thread-0”Id=10在“拥有的java.lang.Object@58fe64b9上阻塞”Thread-1"Id=11deadlock.DeadlockTester$1.run(DeadlockTester.java:28)java.lang.Thread.run(Thread.java:662)请记住,死锁检测可能很昂贵,您需要使用您的程序来测试是否你真的需要死锁检测和多久我建议死锁检测间隔至少几分钟,因为更频繁的检测没有多大意义,因为我们没有一个恢复计划,我们能做的就是调试和处理错误或重新启动程序并希望死锁不再发生。如果您对解决死锁问题有好的建议或者对这个方案有疑问,请在下方留言。