63

很久以前,我从一本Java参考书中保存了一句话:“Java没有处理死锁的机制,它甚至不知道发生了死锁。” (Head First Java 第 2 版,第 516 页)

那么,它是怎么回事呢?有没有办法在 Java 中捕获死锁情况?我的意思是,有没有办法让我们的代码理解发生死锁的情况?

4

16 回答 16

81

从 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);
     }
  }
于 2008-10-20T07:11:24.723 回答
18

JConsole能够检测正在运行的应用程序中的死锁。

于 2008-10-19T23:03:13.867 回答
11

JDK 5 和 6 将在完整的线程转储中转储持有的锁信息(通过 kill -3、jstack、jconsole 等获得)。JDK 6 甚至包含有关 ReentrantLock 和 ReentrantReadWriteLock 的信息。根据这些信息,可以通过查找锁循环来诊断死锁:线程 A 持有锁 1,线程 B 持有锁 2,A 请求 2 或 B 请求 1。根据我的经验,这通常很明显。

其他分析工具实际上可以发现潜在的死锁,即使它们没有发生。来自 OptimizeIt、JProbe、Coverity 等供应商的线程工具是不错的选择。

于 2008-10-20T03:19:30.667 回答
10

请注意,使用并发包时存在一种很难调试的死锁。这就是你有一个 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!");
            }
        }
    }
}
于 2009-11-02T13:20:16.650 回答
6

一般来说,java 不提供死锁检测。与显式锁定语言相比,synchronized 关键字和内置监视器使得死锁的推理更加困难。

我建议迁移到使用 java.util.concurrent.Lock 锁等,以使您的锁定方案更易于推理。事实上,您可以轻松地使用死锁检测来实现自己的锁接口。算法基本就是遍历锁依赖图,寻找一个循环。

于 2008-10-19T23:47:31.157 回答
5

如果您遵循一个简单的规则,就可以避免死锁:让所有线程以相同的顺序声明和释放它们的锁。这样,您永远不会陷入可能发生死锁的情况。

即使是哲学家进餐问题也可以被视为违反了这一规则,因为它使用了左右勺子的相对概念,导致不同的线程使用勺子的不同分配顺序。如果勺子的编号是唯一的,而哲学家们都试图首先得到编号最小的勺子,那么僵局就不可能发生。

在我看来,预防胜于治疗。

这是我喜欢遵循的两个准则之一,以确保线程正常工作。另一个是确保每个线程单独负责自己的执行,因为它是唯一一个完全知道自己在任何时间点正在做什么的线程。

所以这意味着没有Thread.stop调用,使用全局标志(或消息队列或类似的东西)告诉另一个线程你想要采取行动。然后让那个线程做实际的工作。

于 2008-10-20T05:06:13.570 回答
5

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"
于 2011-12-08T20:56:48.160 回答
4

如果您使用的是 Java 5,则可以调用findMonitorDeadlockedThreads()ThreadMXBean 上的方法,您可以通过调用java.lang.management.ManagementFactory.getThreadMXBean(). 这将仅发现由对象监视器引起的死锁。在 Java 6 上findDeadlockedThreads(),还会发现由“可拥有的同步器(例如ReentrandLockReentrantReadWriteLock)引起的死锁。

请注意,调用这些方法可能会很昂贵,因此它们应仅用于故障排除目的。

于 2008-10-20T07:03:28.763 回答
3

JavaSpecialists 的 Heinz Kabutz 博士撰写了一篇关于 Java 死锁的有趣且内容丰富的时事通讯,并在另一时事通讯中描述了称为 ThreadMXBean 的东西。在这些之间,您应该对这些问题有一个很好的了解,并了解一些关于自己进行检测的指示。

于 2008-10-20T03:26:05.053 回答
3

不完全是您所要求的,但是当确实发生死锁时,您可以对进程 ID 执行“kill -3”,并将线程转储转储到标准输出。此外,1.6 jvm 有一些工具可以以 gui 方式做同样的事情。

于 2008-10-19T23:04:17.687 回答
3

如果您从命令行运行并且您怀疑自己已死锁,请尝试在 Windows 中按 ctrl+break(在 unix 中为 ctrl+\)以获取线程转储。见http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/gbmps.html

于 2008-10-19T23:09:56.363 回答
2

如果你在 Eclipse 中调试,你可以暂停应用程序(在调试视图中选择应用程序和调试工具栏上的小 || 按钮),然后它可以报告死锁。

有关示例,请参见http://runnerwhocodes.blogspot.com/2007/10/deadlock-detection-with-eclipse.html

于 2008-10-21T15:23:39.707 回答
1

jcmd从 Java 1.8 开始,您可以通过在终端上使用命令轻松查看您的程序是否存在死锁。

  1. 在您的终端上运行jcmd:它列出了所有使用 Java 的程序(PID 和名称)。
  2. 现在,运行jcmd <PID> Thread.print:这将在您的控制台上打印线程转储,并且如果您的程序有死锁,也会打印。

jcmd您可以使用命令列出的 更多选项来分析您的 Java 程序jcmd <PID> help

于 2020-06-22T16:48:47.290 回答
0

Java 5 引入了 ThreadMXBean——一个为线程提供各种监控方法的接口。...不同的是findDeadlockedThreads也可以检测到由所有者锁(java.util.concurrent)引起的死锁,而findMonitorDeadlockedThreads只能检测监控锁(即同步块)

或者您可以以编程方式检测它,请参阅此https://dzone.com/articles/how-detect-java-deadlocks

于 2017-09-19T05:05:50.203 回答
-1

经过这么长时间,我能够写出最简单的死锁示例。欢迎评论。

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();
  }
}
于 2010-05-24T14:27:37.343 回答
-4

你必须在死锁类中稍微修改一下代码

   死锁(){
    Therad t = 新线程(此);// 修改的
    t.start();
    System.out.println(); //任何延迟指令
    a.方法A(b);
}

此外,上面的代码并不总是会导致死锁,只是有时会发生。

于 2011-05-08T12:35:34.877 回答