本文由 ImportNew - rookie_sam 翻译自 Dzone。欢迎加入翻译小组。转载请见文末要求。

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

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

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

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

1
2
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
this.scheduler.scheduleAtFixedRate(deadlockCheck, period, period, unit);

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

1
void handleDeadlock(final ThreadInfo[] deadlockedThreads);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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输出死锁线程的信息。在现实场景中,我们可以用它发送邮件,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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,我们需要警惕这种情况。最后,让我们促成一个死锁来看看系统是如何运行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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();

输出:

1
2
3
4
5
6
7
8
9
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)

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

原文链接: Dzone 翻译: ImportNew.comrookie_sam
译文链接: http://www.importnew.com/15307.html

如何通过编程发现Java死锁的更多相关文章

  1. 通过编程发现Java死锁

    通过stack也可以发现死锁. 测试类 import java.util.concurrent.TimeUnit; public class Test { public static void mai ...

  2. Java并发编程实战 04死锁了怎么办?

    Java并发编程文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 Java并发编程实战 03互斥锁 解决原子性问题 前提 在第三篇 ...

  3. Java多线程编程(同步、死锁、生产消费者问题)

    Java多线程编程(同步.死锁.生产消费): 关于线程同步以及死锁问题: 线程同步概念:是指若干个线程对象并行进行资源的访问时实现的资源处理保护操作: 线程死锁概念:是指两个线程都在等待对方先完成,造 ...

  4. java 死锁及解决

    Java线程死锁如何避免这一悲剧  Java线程死锁需要如何解决,这个问题一直在我们不断的使用中需要只有不断的关键.不幸的是,使用上锁会带来其他问题.让我们来看一些常见问题以及相应的解决方法: Jav ...

  5. Java死锁及解决

    Java线程死锁如何避免这一悲剧  Java线程死锁需要如何解决,这个问题一直在我们不断的使用中需要只有不断的关键.不幸的是,使用上锁会带来其他问题.让我们来看一些常见问题以及相应的解决方法: Jav ...

  6. 一文学会Java死锁和CPU 100% 问题的排查技巧

    做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开 工欲善其事,必先利其器 00 本文简介 作为一名搞技术的程序猿或者是攻城狮,想必你应该是对下面这两个问题有所了解,说不定你在 ...

  7. 实例详解 Java 死锁与破解死锁

    锁和被保护资源之间的关系 我们把一段需要互斥执行的代码称为临界区.线程在进入临界区之前,首先尝试加锁 lock(),如果成功,则进入临界区,此时我们称这个线程持有锁:否则呢就等待,直到持有锁的线程解锁 ...

  8. 为什么函数式编程在Java中很危险?

    摘要:函数式编程这个不温不火的语言由来已久.有人说,这一年它会很火,尽管它很难,这也正是你需要学习的理由.那么,为什么函数式编程在Java中很危险呢?也许这个疑问普遍存在于很多程序员的脑中,作者Ell ...

  9. Java并发编程:Java的四种线程池的使用,以及自定义线程工厂

    目录 引言 四种线程池 newCachedThreadPool:可缓存的线程池 newFixedThreadPool:定长线程池 newSingleThreadExecutor:单线程线程池 newS ...

随机推荐

  1. SQL SELECT DISTINCT

    SQL SELECT DISTINCT(选择不同) 语法 SELECT DISTINCT语法用于仅返回不同的(different)值. 在一张表内,一列通常包含许多重复的值; 有时你只想列出不同的(d ...

  2. RzGroupBar

    何分多层 procedure TForm1.FormCreate(Sender: TObject); begin RzGroup1.Items.Clear; RzGroup1.Items.Add.Ca ...

  3. web服务nginx和php的相互关系

    nginx和php有什么关系?很多新手可能有这个疑问,我之前学php也没注意这些问题,只管着按文档配置操作,完成php项目就不管了,最近特意总结了一下. php是一门编程语言,讲究说学逗唱...呃,不 ...

  4. CentOS7简单安装mplayer和vlc!

    http://pkgs.org/在这个网上搜索下面的包的最新版1. sudo rpm -ivh epel-release-7-0.2.noarch.rpm 2. sudo rpm -Uvh elrep ...

  5. STM32嵌入式开发学习笔记(四):使用滴答计时器实现精准计时

    前面我们讲过,因为在STM32上没有系统时间的接口,因此无法调用sleep函数,在本文中,笔者将利用滴答计时器实现精准延时. 查阅技术手册,滴答计时器依赖于一个SysTick_Type类型寄存器,定义 ...

  6. C#调用Mail发送QQ邮件

    需要用到: 1.System.Net.Mail; 2.QQ邮箱的POP3/SMTP服务码 QQ邮箱的POP3/SMTP服务码获取方法: 1.打开qq邮箱: 2.进入设置页面-->账户:(往下翻) ...

  7. C++——异常

    1.throw表达式 if (item1.isbn()!=item2.isbn()) throw runtime_error("data must refer to the same ISB ...

  8. POJ 1673 EXOCENTER OF A TRIANGLE(解三角形重心)

    题目链接:http://poj.org/problem?id=1673 AC代码: #include<cstdio> #include<cmath> #include<a ...

  9. POJ 1329 Circle Through Three Points(三角形外接圆)

    题目链接:http://poj.org/problem?id=1329 #include<cstdio> #include<cmath> #include<algorit ...

  10. java实现邮件定时发送

    最近做项目时客户提出了一个需求:系统定时发送E-mail到其客户,达到通知的效果.先将实例分享给大家,如果确实有一些帮助的话,请大家来点掌声! 首先介绍java定时器(java.util.Timer) ...