Java中的死锁检测
很久以前,我从一本Java参考书中保存了一句话: “Java没有处理死锁的机制,甚至不知道发生了死锁。 (Head First Java第2版,第516页)
那么,这是什么呢? 有没有办法在Java中捕获死锁的情况? 我的意思是,有没有一种方法可以让我们的代码理解一个僵局?
由于JDK 1.5在java.lang.management
包中有非常有用的方法来查找和检查发生的死锁。 请参阅ThreadMXBean
类的findMonitorDeadlockedThreads()
和findDeadlockedThreads()
方法。
一个可能的方法是使用这个独立的看门狗线程(或周期性任务)。
示例代码:
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); } }
JConsole能够检测正在运行的应用程序中的死锁。
JDK 5和6会将持有的locking信息转储到完整的线程转储(通过kill -3,jstack,jconsole等获得)。 JDK 6甚至包含有关ReentrantLock和ReentrantReadWriteLock的信息。 从这个信息可以通过find一个locking循环来诊断死锁:线程A保存locking1,线程B保存locking2,并且A请求2或者B请求1.从我的经验来看,这通常是非常明显的。
其他分析工具实际上可以发现潜在的死锁,即使它们没有发生。 像OptimizeIt,JProbe,Coverity等供应商的线程工具是很好的地方。
请注意,使用非常难以debugging的并发程序包存在一种types的死锁。 这就是你有一个ReentrantReadWriteLock的地方,一个线程抓取读锁,然后(比方说)试图进入一个其他线程也等待抓取写锁的监视器。 是什么让它特别难debugging是谁没有进入读锁的logging。 这只是一个计数。 线程甚至可能抛出一个exception,并使得读取计数值不为零。
下面是前面提到的findDeadlockedThreads方法不会得到的示例死锁:
import java.util.concurrent.locks.*; import java.lang.management.*; public class LockTest { static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public static void main(String[] args) throws Exception { Reader reader = new Reader(); Writer writer = new Writer(); sleep(10); System.out.println("finding deadlocked threads"); 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); } } System.out.println("finished finding deadlocked threads"); } static void sleep(int seconds) { try { Thread.currentThread().sleep(seconds*1000); } catch (InterruptedException e) {} } static class Reader implements Runnable { Reader() { new Thread(this).start(); } public void run() { sleep(2); System.out.println("reader thread getting lock"); lock.readLock().lock(); System.out.println("reader thread got lock"); synchronized (lock) { System.out.println("reader thread inside monitor!"); lock.readLock().unlock(); } } } static class Writer implements Runnable { Writer() { new Thread(this).start(); } public void run() { synchronized (lock) { sleep(4); System.out.println("writer thread getting lock"); lock.writeLock().lock(); System.out.println("writer thread got lock!"); } } } }
一般来说java不提供死锁检测。 同步关键字和内置的监视器使得比具有显式locking的语言更难以推断死锁。
我build议迁移到使用java.util.concurrent.Lock锁等,以使您的lockingscheme更容易推理。 事实上,你可以很容易地制定你自己的locking接口死锁检测。 该algorithm是基本遍历锁依赖图并寻找一个循环。
如果您使用的是Java 5,则可以调用ThreadMXBean上的方法findMonitorDeadlockedThreads()
,您可以通过调用java.lang.management.ManagementFactory.getThreadMXBean()
来获取该方法。 这只会发现由对象监视器造成的死锁。 在Java 6上有findDeadlockedThreads()
,它也会发现由“可拥有的同步器(例如ReentrandLock
和ReentrantReadWriteLock
)造成的死锁。
请注意,调用这些方法可能会很昂贵,因此只能用于故障排除。
不完全是你问的,但是当发生死锁时,你可以在进程ID上做一个“kill -3”,并把一个线程转储转储到标准输出。 另外,1.6 jvm有一些工具可以用gui的方式来做同样的事情。
如果您正在从命令行运行,并且怀疑已经死锁,请尝试在Windows中按Ctrl + Break(ctrl + \ in unix)以获取线程转储。 请参阅http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/gbmps.html
JavaSpecialists的Heinz Kabutz博士撰写了一个关于Java死锁的娱乐性和信息性的新闻通讯,并在另一个通讯期刊中描述了一个叫做ThreadMXBean的东西。 在这些之间,你应该对这些问题有一个清楚的认识,并且指导你做一些你自己的仪器。
Java可以检测死锁(尽pipe不是在运行时,它仍然可以诊断并报告)。
例如,当使用“Saurabh M. Chande”代码稍微修改后的版本(将其更改为Java并添加了一些时间来保证每次运行时locking)。 一旦你运行它,它会死锁,如果你input:
kill -3 PID # where 'PID' is the Linux process ID
它会生成一个堆栈转储,其中将包含以下信息:
Found one Java-level deadlock: ============================= "Thread-0": waiting to lock monitor 0x08081670 (object 0x7f61ddb8, a Deadlock$A), which is held by "main" "main": waiting to lock monitor 0x080809f0 (object 0x7f61f3b0, a Deadlock$B), which is held by "Thread-0"
如果遵循一个简单的规则,可以避免死锁:让所有线程声明并以相同的顺序释放它们的锁。 这样,你永远不会陷入可能发生死锁的情况。
甚至餐饮哲学家的问题也可以被看作是违反了这个规则,因为它使用左右匙的相对概念,这导致不同的线程使用不同的匙子分配顺序。 如果勺子的编号是独一无二的,而哲学家们都试图先拿到最小的勺子,那么僵局是不可能的。
在我看来,预防胜于治疗。
这是我喜欢遵循的两个准则之一,以确保线程正常工作。 另一个是确保每个线程完全负责自己的执行,因为它是唯一一个完全知道它在任何时间点正在做什么的人。
因此,这意味着没有Thread.stop
调用,使用全局标志(或消息队列或类似的东西)告诉另一个线程,你想要采取行动。 然后让那个线程去做实际的工作。
如果在eclipse中debugging,可以暂停应用程序(在debugging视图中select应用程序,在debugging工具栏上select小的||button),然后报告死锁。
有关示例,请参阅http://runnerwhocodes.blogspot.com/2007/10/deadlock-detection-with-eclipse.html 。
经过这么长时间,我能写出最简单的死锁例子。 欢迎评论。
Class A { synchronized void methodA(B b) { b.last(); } synchronized void last() { SOP(“ Inside A.last()”); } } Class B { synchronized void methodB(A a) { a.last(); } synchronized void last() { SOP(“ Inside B.last()”); } } Class Deadlock implements Runnable { A a = new A(); B b = new B(); // Constructor Deadlock() { Thread t = new Thread(); t.start(); a.methodA(b); } public void run() { b.methodB(a); } public static void main(String args[] ) { new Deadlock(); } }
Java 5引入了ThreadMXBean – 为线程提供各种监视方法的接口。 …区别在于findDeadlockedThreads还可以检测到由所有者锁(java.util.concurrent)引起的死锁,而findMonitorDeadlockedThreads只能检测监视器锁(即同步块)
或者你可以通过编程方式检测到,请参考https://dzone.com/articles/how-detect-java-deadlocks
你必须在死锁类中稍微修改一下代码
死锁(){ Therad t =新主题(this); //修改 t.start(); 的System.out.println(); //任何延迟的指令 a.methodA(b)中; }
而且上面的代码并不总是会导致死锁,只有一些时候会发生。