什么情况下Java程序会产生死锁?
循环依赖,彼此一直处于等待状态,并且互相都没有进展。死锁不仅仅是在线程之间会发生,存在资源独占的进程之间同样 也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。
如果程序运行时发生了死锁,绝大多数情况下都是无法在线解决的,只能重启、修正程序本身问题。所以,代码开发阶段互相审查,或者利用工具进行预防性排查,往往也是很重要的。
举例分析
package Test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class DeadThread extends Thread{ private Object obj1; private Object obj2; private int order; public DeadThread(int order, Object obj1, Object obj2) { this.order = order; this.obj1 = obj1; this.obj2 = obj2; } public void test1() throws InterruptedException { synchronized (obj1) { //建议线程调取器切换到其它线程运行 Thread.yield(); synchronized (obj2) { System.out.println("test。。。"); } } } public void test2() throws InterruptedException { synchronized (obj2) { Thread.yield(); synchronized (obj1) { System.out.println("test。。。"); } } } @Override public void run() { while (true) { try { if(this.order == 1){ this.test1(); }else{ this.test2(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } class Test { public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); Object obj2 = new Object(); ExecutorService ex = Executors.newFixedThreadPool(10); // 起10个线程 for (int i = 0; i < 10; i++) { int order = i%2==0 ? 1 : 0; ex.execute(new DeadThread(order, obj1, obj2)); } } }
这段就是典型的死锁代码,我们如何通过工具而不是人眼识别? jstack,还有一些图形化工具,这里只讲前者
用jstack发现死锁
首先jps,这个命令就是显示java的进程pid
这是没有运行上面死锁代码的,我们运行一下再jps
可以看出相关pid为11100,然后我们再用jstack分析
可以看出分析的结果,然后我们就可以从相关位置处查看分析了。
分析的思路大概就是
区分线程状态 -> 查看等待目标 -> 对比Monitor等持有状态
用ThreadMXBean来发现死锁
import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; public class DeadLockTesting { public static void main(String args[]) { Object obj1 = new Object(); Object obj2 = new Object(); DeadLockThread t1 = new DeadLockThread(obj1, obj2); DeadLockThread t2 = new DeadLockThread(obj2, obj1); t1.start(); t2.start(); try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } MonitorThread mt = new MonitorThread(); mt.start(); } } class MonitorThread extends Thread { public void run() { ThreadMXBean tmx = ManagementFactory.getThreadMXBean(); long[] ids = tmx.findDeadlockedThreads(); if (ids != null) { ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true); System.out.println("The following threads are deadlocked:"); for (ThreadInfo ti : infos) { System.out.println(ti); } } } } class DeadLockThread extends Thread { private Object obj1; private Object obj2; public DeadLockThread(Object obj1, Object obj2) { this.obj1 = obj1; this.obj2 = obj2; } public void run() { synchronized (obj1) { try { Thread.sleep(1000); synchronized (obj2) { } } catch (Exception e) { e.printStackTrace(); } } } }
如何在编程中尽量预防死锁呢?
互斥条件,类似Java中Monitor都是独占的,要么是我用,要么是你用。
互斥条件是长期持有的,在使用结束之前,自己不会释放,也不能被其他线程抢占。
循环依赖关系,两个或者多个个体之间出现了锁的链条环。
第一种方法
如果可能的话,尽量避免使用多个锁,并且只有需要时才持有锁。
第二种方法
如果必须使用多个锁,尽量设计好锁的获取顺序,这个说起来简单,做起来可不容易,你可以参看著名的银行家算法。
银行家算法
将对象(方法)和锁之间的关系,用图形化的方式表示分别抽取出来,以今天最初讲的死锁为例,因为是调用了同一个线程所以更加简单。
很清晰就发现这有死锁的风险
第三种方法
使用带超时的方法,为程序带来更多可控性。
类似Object.wait(…)或者CountDownLatch.await(…),都支持所谓的timed_wait,我们完全可以就不假定该锁一定会获得,指定超时时间,并为无法得到锁时准备退出逻辑。
if (lock.tryLock() || lock.tryLock(timeout, unit)) { // ... }
第四种方法
业界也有一些其他方面的尝试,比如通过静态代码分析(如FindBugs)去查找固定的模式,进而定位可能的死锁或者竞争情况。
findbugs是一个好用的插件,可以自行搜索使用
技巧
比如Linux上,可以使用top命令配合grep Java之类,找到忙的pid;然后,转换成16进制,就是jstack输出中的格式;再定位代码
任务线程规范命名,详细记录逻辑运行日志。jstack查看线程状态。
参考杨晓峰老师的《java核心技术》