通过编程使用ThreadMXBean类发现Java死锁方法实例

死锁是指,两个或多个动作一直在等待其他动作完成而使得所有动作都始终处在阻塞的状态。想要在开发阶段检测到死锁是非常困难的,而想要解除死锁往往需要重新启动程序。更糟的是,死锁通常发生在负载最重的生产过程中,而想要在测试中发现它,十分不易。之所以这么说,是因为测试线程之间所有可能的交叉是不现实的。尽管出现了一些静态分析库可以帮助我们发现可能出现的死锁,我们还是有必要在运行时检测到死锁,并且得到有用的信息,以便我们解决这个问题或者重启程序,或者做些其他的事情。

在编程中使用ThreadMXBean类来检测死锁

Java 5引入了ThreadMXBean接口,它提供了多种监视线程的方法。我建议您了解所有这些方法,因为当您没使用外部工具时,它们会为您提供很多有用的操作以便您监测程序性能。这里,我们感兴趣的方法是findMonitorDeadlockedThreads,如过您使用的是Java 6,对应的方法是findDeadlockedThreads。二者的区别的是,findDeadlockedThreads还可以检测到owner locks(java.util.concurrent)引起的死锁,而findMonitorDeadlockedThreads只能检测monitor locks(例如,同步块)。由于保留老版本的方法只是出于兼容性的考虑,所以我将使用新版本的方法。在这里,编程的思想是把对死锁的周期性检测封装到一个可重用组件里,之后我们只需启动它、随它去。

一种实现调度的方法是通过执行器框架,即一组良好抽象并易于使用的多线程类。

 代码如下 复制代码
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
this.scheduler.scheduleAtFixedRate(deadlockCheck, period, period, unit);

就是那么简单,在我们通过选择周期和时间单位而设置了一个特定时间后,就得到了一个周期性调用的线程。接着,我们想使功用得以拓展从而允许用户提供在程序检测到死锁时所触发的行为。最后,我们需要一个方法来接收用于描述死锁中所有线程的一系列对象。

 代码如下 复制代码
void handleDeadlock(final ThreadInfo[] deadlockedThreads);

现在,实现死锁检测类已经万事俱备了。

 代码如下 复制代码
public interface DeadlockHandler {
  void handleDeadlock(final ThreadInfo[] deadlockedThreads);
}
 
public class DeadlockDetector {
 
  private final DeadlockHandler deadlockHandler;
  private final long period;
  private final TimeUnit unit;
  private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
  private final ScheduledExecutorService scheduler =
  Executors.newScheduledThreadPool(1);
 
  final Runnable deadlockCheck = new Runnable() {
    @Override
    public void run() {
      long[] deadlockedThreadIds = DeadlockDetector.this.mbean.findDeadlockedThreads();
 
      if (deadlockedThreadIds != null) {
        ThreadInfo[] threadInfos =
        DeadlockDetector.this.mbean.getThreadInfo(deadlockedThreadIds);
 
        DeadlockDetector.this.deadlockHandler.handleDeadlock(threadInfos);
      }
    }
  };
 
  public DeadlockDetector(final DeadlockHandler deadlockHandler,
    final long period, final TimeUnit unit) {
    this.deadlockHandler = deadlockHandler;
    this.period = period;
    this.unit = unit;
  }
 
  public void start() {
    this.scheduler.scheduleAtFixedRate(
    this.deadlockCheck, this.period, this.period, this.unit);
  }
}

让我们动手试试。首先,我们要创建一个handler用来向System.err输出死锁线程的信息。在现实场景中,我们可以用它发送邮件,比如:

 代码如下 复制代码
public class DeadlockConsoleHandler implements DeadlockHandler {
 
  @Override
  public void handleDeadlock(final ThreadInfo[] deadlockedThreads) {
    if (deadlockedThreads != null) {
      System.err.println("Deadlock detected!");
 
      Map<Thread, StackTraceElement[]> stackTraceMap = Thread.getAllStackTraces();
      for (ThreadInfo threadInfo : deadlockedThreads) {
 
        if (threadInfo != null) {
 
          for (Thread thread : Thread.getAllStackTraces().keySet()) {
 
            if (thread.getId() == threadInfo.getThreadId()) {
              System.err.println(threadInfo.toString().trim());
 
              for (StackTraceElement ste : thread.getStackTrace()) {
                  System.err.println("t" + ste.toString().trim());
              }
            }
          }
        }
      }
    }
  }
}

这一过程在所有的堆栈追踪中反复进行并为每个线程信息打印对应的堆栈踪迹。通过这种方式,我们可以准确知道每个线程等待的位置和对象。但这个方法有一个缺陷——当一个线程只是暂时等待时,可能会被当作一个暂时的死锁,从而引发错误的警报。出于此,当我们处理死锁时,原始线程不能继续存在而findDeadlockedThreads方法会返回没有此类线程。为了避免可能出现的NullPointerException,我们需要警惕这种情况。最后,让我们促成一个死锁来看看系统是如何运行的。

 代码如下 复制代码
DeadlockDetector deadlockDetector = new DeadlockDetector(new DeadlockConsoleHandler(), 5, TimeUnit.SECONDS);
deadlockDetector.start();
 
final Object lock1 = new Object();
final Object lock2 = new Object();
 
Thread thread1 = new Thread(new Runnable() {
  @Override
  public void run() {
    synchronized (lock1) {
      System.out.println("Thread1 acquired lock1");
      try {
        TimeUnit.MILLISECONDS.sleep(500);
      } catch (InterruptedException ignore) {
      }
      synchronized (lock2) {
        System.out.println("Thread1 acquired lock2");
      }
    }
  }
 
});
thread1.start();
 
Thread thread2 = new Thread(new Runnable() {
  @Override
  public void run() {
    synchronized (lock2) {
      System.out.println("Thread2 acquired lock2");
      synchronized (lock1) {
        System.out.println("Thread2 acquired lock1");
      }
    }
  }
});
thread2.start();

输出:

 代码如下 复制代码
Thread1 acquired lock1
Thread2 acquired lock2
Deadlock detected!
“Thread-1” Id=11 BLOCKED on java.lang.Object@68ab95e6 owned by “Thread-0” Id=10
deadlock.DeadlockTester$2.run(DeadlockTester.java:42)
 java.lang.Thread.run(Thread.java:662)
“Thread-0” Id=10 BLOCKED on java.lang.Object@58fe64b9 owned by “Thread-1” Id=11
 deadlock.DeadlockTester$1.run(DeadlockTester.java:28)
 java.lang.Thread.run(Thread.java:662)

记住,死锁检测的开销可能会很大,你需要用你的程序来测试一下你是否真的需要死锁检测以及多久检测一次。我建议死锁检测的时间间隔至少为几分钟,因为更加频繁的检测并没有太大的意义,原因是我们并没有一个复原计划,我们能做的只是调试和处理错误或者重启程序并祈祷不会再次发生死锁。

时间: 2024-09-27 04:51:30

通过编程使用ThreadMXBean类发现Java死锁方法实例的相关文章

如何通过编程发现Java死锁

死锁是指,两个或多个动作一直在等待其他动作完成而使得所有动作都始终处在阻塞的状态.想要在开发阶段检测到死锁是非常困难的,而想要解除死锁往往需要重新启动程序.更糟的是,死锁通常发生在负载最重的生产过程中,而想要在测试中发现它,十分不易.之所以这么说,是因为测试线程之间所有可能的交叉是不现实的.尽管出现了一些静态分析库可以帮助我们发现可能出现的死锁,我们还是有必要在运行时检测到死锁,并且得到有用的信息,以便我们解决这个问题或者重启程序,或者做些其他的事情. 在编程中使用ThreadMXBean类来检

java类的问题-java编程,有关类的继承,方法的覆盖等

问题描述 java编程,有关类的继承,方法的覆盖等 类Citizen,拥有identityCardNumber(String).name(String).gender(char).birthPlace(String).birthday(java.util.Date)属性,构造方法Citizen(String identityCardNumber, String name, char gender).Citizen(String identityCardNumber, String name, c

C++设计类不能被继承的方法实例讲解

 在Java 中定义了关键字final,被final修饰的类不能被继承,C++中如何实现,下面我们来看一个例子 首先想到的是在C++中,子类的构造函数会自动调用父类的构造函数.同样,子类的析构函数也会自动调用父类的析构函数.要想一个类不能被继承,只要把它的构造函数和析构函数都定义为私有函数.那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数.析构函数而导致编译错误.   可是这个类的构造函数和析构函数都是私有函数了,怎样才能得到该类的实例呢?可以通过定义静态来创建和释放类的实例.基于这

python避免死锁方法实例分析

  本文实例讲述了python避免死锁方法.分享给大家供大家参考.具体分析如下: 当两个或者更多的线程在等待资源的时候就会产生死锁,两个线程相互等待. 在本文实例中 thread1 等待thread2释放block , thread2等待thtead1释放ablock, 避免死锁的原则: 1. 一定要以一个固定的顺序来取得锁,这个列子中,意味着首先要取得alock, 然后再去block 2. 一定要按照与取得锁相反的顺序释放锁,这里,应该先释放block,然后是alock ? 1 2 3 4 5

C++设计类不能被继承的方法实例讲解_C 语言

首先想到的是在C++中,子类的构造函数会自动调用父类的构造函数.同样,子类的析构函数也会自动调用父类的析构函数.要想一个类不能被继承,只要把它的构造函数和析构函数都定义为私有函数.那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数.析构函数而导致编译错误. 可是这个类的构造函数和析构函数都是私有函数了,怎样才能得到该类的实例呢?可以通过定义静态来创建和释放类的实例.基于这个思路,可以写出如下的代码: 复制代码 代码如下: ////////////////////////////////

Linux设备驱动编程---miscdevice杂类设备的使用方法

miscdev简称杂类设备杂类设备就是对字符设备驱动做一个封装,方便简单使用杂类设备封装字符设备需要包含的头文件:#include <linux/miscdevice.h>(1)杂类设备的数据结构: struct miscdevice { int minor; //次设备号 const char *name; //设备名称 const struct file_operations *fops; //文件操作结构体 struct list_head list; struct device *pa

Thinkphp调用Image类生成缩略图的方法

 这篇文章主要介绍了Thinkphp调用Image类生成缩略图的方法,实例分析了Thinkphp调用Image类生成缩略图的使用原理与相关技巧,需要的朋友可以参考下     本文实例讲述了Thinkphp调用Image类生成缩略图的方法.分享给大家供大家参考.具体分析如下: Thinkphp的Image类 在ThinkPHP/Extend/Library/ORG/Util/Image.class.php中. 调用方法如下: ? 1 2 3 4 5 6 7 import("ORG.Util.Ima

Java并发编程:Thread类的使用

在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知识:线程的几种状态.上下文切换,然后接着介绍Thread类中的方法的具体使用. 以下是本文的目录大纲: 一.线程的状态 二.上下文切换 三.Thread类中的方法 若有不正之处,请多多谅解并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接:   http://www.cnblogs.com/dolphin0520/p/3920357

Java线程编程中Thread类的基础学习教程_java

一.线程的状态 在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解. 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time waiting.waiting.消亡(dead). 当需要新起一个线程来执行某个子任务时,就创建了一个线程.但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内