很久以前,我从一本Java参考书中保存了一句话:“Java没有处理死锁的机制,它甚至不知道发生了死锁。” (Head First Java 第 2 版,第 516 页)
那么,它是怎么回事呢?有没有办法在 Java 中捕获死锁情况?我的意思是,有没有办法让我们的代码理解发生死锁的情况?
很久以前,我从一本Java参考书中保存了一句话:“Java没有处理死锁的机制,它甚至不知道发生了死锁。” (Head First Java 第 2 版,第 516 页)
那么,它是怎么回事呢?有没有办法在 Java 中捕获死锁情况?我的意思是,有没有办法让我们的代码理解发生死锁的情况?
从 JDK 1.5 开始,包中有非常有用的方法java.lang.management
来查找和检查发生的死锁。查看类的findMonitorDeadlockedThreads()
andfindDeadlockedThreads()
方法ThreadMXBean
。
一种可能的使用方法是有一个单独的看门狗线程(或周期性任务)来执行此操作。
示例代码:
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 将在完整的线程转储中转储持有的锁信息(通过 kill -3、jstack、jconsole 等获得)。JDK 6 甚至包含有关 ReentrantLock 和 ReentrantReadWriteLock 的信息。根据这些信息,可以通过查找锁循环来诊断死锁:线程 A 持有锁 1,线程 B 持有锁 2,A 请求 2 或 B 请求 1。根据我的经验,这通常很明显。
其他分析工具实际上可以发现潜在的死锁,即使它们没有发生。来自 OptimizeIt、JProbe、Coverity 等供应商的线程工具是不错的选择。
请注意,使用并发包时存在一种很难调试的死锁。这就是你有一个 ReentrantReadWriteLock 并且一个线程获取读锁的地方,然后(比如说)尝试进入一个由其他线程持有的监视器,该线程也在等待获取写锁。使调试特别困难的是没有记录谁进入了读锁。这只是一个计数。线程甚至可能抛出异常并死亡,使读取计数非零。
以下是前面提到的 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 不提供死锁检测。与显式锁定语言相比,synchronized 关键字和内置监视器使得死锁的推理更加困难。
我建议迁移到使用 java.util.concurrent.Lock 锁等,以使您的锁定方案更易于推理。事实上,您可以轻松地使用死锁检测来实现自己的锁接口。算法基本就是遍历锁依赖图,寻找一个循环。
如果您遵循一个简单的规则,就可以避免死锁:让所有线程以相同的顺序声明和释放它们的锁。这样,您永远不会陷入可能发生死锁的情况。
即使是哲学家进餐问题也可以被视为违反了这一规则,因为它使用了左右勺子的相对概念,导致不同的线程使用勺子的不同分配顺序。如果勺子的编号是唯一的,而哲学家们都试图首先得到编号最小的勺子,那么僵局就不可能发生。
在我看来,预防胜于治疗。
这是我喜欢遵循的两个准则之一,以确保线程正常工作。另一个是确保每个线程单独负责自己的执行,因为它是唯一一个完全知道自己在任何时间点正在做什么的线程。
所以这意味着没有Thread.stop
调用,使用全局标志(或消息队列或类似的东西)告诉另一个线程你想要采取行动。然后让那个线程做实际的工作。
Java 可以检测死锁(虽然不是在运行时,但它仍然可以诊断和报告它)。
例如,当使用稍微修改版本的“Saurabh M. Chande”代码时(将其更改为 Java 并添加了一些时间以保证每次运行时锁定)。一旦你运行它并且它死锁,如果你输入:
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"
如果您使用的是 Java 5,则可以调用findMonitorDeadlockedThreads()
ThreadMXBean 上的方法,您可以通过调用java.lang.management.ManagementFactory.getThreadMXBean()
. 这将仅发现由对象监视器引起的死锁。在 Java 6 上findDeadlockedThreads()
,还会发现由“可拥有的同步器(例如ReentrandLock
和ReentrantReadWriteLock
)引起的死锁。
请注意,调用这些方法可能会很昂贵,因此它们应仅用于故障排除目的。
JavaSpecialists 的 Heinz Kabutz 博士撰写了一篇关于 Java 死锁的有趣且内容丰富的时事通讯,并在另一时事通讯中描述了称为 ThreadMXBean 的东西。在这些之间,您应该对这些问题有一个很好的了解,并了解一些关于自己进行检测的指示。
不完全是您所要求的,但是当确实发生死锁时,您可以对进程 ID 执行“kill -3”,并将线程转储转储到标准输出。此外,1.6 jvm 有一些工具可以以 gui 方式做同样的事情。
如果您从命令行运行并且您怀疑自己已死锁,请尝试在 Windows 中按 ctrl+break(在 unix 中为 ctrl+\)以获取线程转储。见http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/gbmps.html
如果你在 Eclipse 中调试,你可以暂停应用程序(在调试视图中选择应用程序和调试工具栏上的小 || 按钮),然后它可以报告死锁。
有关示例,请参见http://runnerwhocodes.blogspot.com/2007/10/deadlock-detection-with-eclipse.html。
jcmd
从 Java 1.8 开始,您可以通过在终端上使用命令轻松查看您的程序是否存在死锁。
jcmd
:它列出了所有使用 Java 的程序(PID 和名称)。jcmd <PID> Thread.print
:这将在您的控制台上打印线程转储,并且如果您的程序有死锁,也会打印。jcmd
您可以使用命令列出的 更多选项来分析您的 Java 程序jcmd <PID> help
Java 5 引入了 ThreadMXBean——一个为线程提供各种监控方法的接口。...不同的是findDeadlockedThreads也可以检测到由所有者锁(java.util.concurrent)引起的死锁,而findMonitorDeadlockedThreads只能检测监控锁(即同步块)
或者您可以以编程方式检测它,请参阅此https://dzone.com/articles/how-detect-java-deadlocks
经过这么长时间,我能够写出最简单的死锁示例。欢迎评论。
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();
}
}
你必须在死锁类中稍微修改一下代码
死锁(){ Therad t = 新线程(此);// 修改的 t.start(); System.out.println(); //任何延迟指令 a.方法A(b); }
此外,上面的代码并不总是会导致死锁,只是有时会发生。