如何通过编程发现Java死锁

本文由 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

时间: 2024-10-11 06:05:29

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

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

摘要:函数式编程这个不温不火的语言由来已久.有人说,这一年它会很火,尽管它很难,这也正是你需要学习的理由.那么,为什么函数式编程在Java中很危险呢?也许这个疑问普遍存在于很多程序员的脑中,作者Elliotte对此发表了一些见解,我们一起来看看他是怎么说的. 在我的日常工作中,我身边的开发者大多是毕业于CS编程顶级院校比如MIT.CMU以及Chicago,他们初次涉及的语言是Haskell.Scheme及Lisp.他们认为函数式编程是一种自然的.直观的.美丽的且高效的编程样式.但奇怪的是,我和我

71.JAVA编程思想——JAVA与CGI

71.JAVA编程思想--JAVA与CGI Java 程序可向一个服务器发出一个CGI 请求,这与HTML 表单页没什么两样.而且和HTML 页一样,这个请求既可以设为GET(下载),亦可设为POST(上传).除此以外,Java 程序还可拦截CGI 程序的输出,所以不必依赖程序来格式化一个新页,也不必在出错的时候强迫用户从一个页回转到另一个页.事实上,程序的外观可以做得跟以前的版本别无二致. 代码也要简单一些,毕竟用CGI 也不是很难就能写出来(前提是真正地理解它).所以我们准备办个CGI 编程

81.JAVA编程思想——JAVA编程规则

81.JAVA编程思想--JAVA编程规则 (1) 类名首字母应该大写.字段.方法以及对象(句柄)的首字母应小写.对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母.例如: ThisIsAClassName thisIsMethodOrFieldName 若在定义中出现了常数初始化字符,则大写static final 基本类型标识符中的所有字母.这样便可标志出它们属于编译期的常数. Java 包(Package)属于一种特殊情况:它们全都是小写字母,即便中间的单词亦是如此

Java死锁范例以及如何分析死锁(转载自ImportNew)

本文由 ImportNew - 范琦琦 翻译自 journaldev.欢迎加入翻译小组.转载请见文末要求. 死锁是两个甚至多个线程被永久阻塞时的一种运行局面,这种局面的生成伴随着至少两个线程和两个或者多个资源.在这里我已写好一个简单的程序,它将会引起死锁方案然后我们就会明白如何分析它. Java死锁范例 ThreadDeadlock.java 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

33.JAVA编程思想——JAVA IO File类

33.JAVA编程思想--JAVA IO File类 RandomAccessFile用于包括了已知长度记录的文件.以便我们能用 seek()从一条记录移至还有一条:然后读取或改动那些记录. 各记录的长度并不一定同样:仅仅要知道它们有多大以及置于文件何处就可以. 首先.我们有点难以相信RandomAccessFile 不属于InputStream 或者OutputStream 分层结构的一部分.除了恰巧实现了DataInput 以及DataOutput(这两者亦由 DataInputStream

Java死锁排查和Java CPU 100% 排查的步骤整理

工欲善其事,必先利其器 简介 本篇整理两个排查问题的简单技巧,一个是java死锁排查,这个一般在面试的时会问到,如果没有写多线程的话,实际中遇到的机会不多:第二个是java cpu 100%排查,这个实际的开发中,线的应用出现这个问题可能性比较大,所以这里简单总结介绍一下,对自己学习知识的一个整理,提高自己的解决问题能力. 一.Java死锁排查 通过标题我们就要思考三个问题: 什么是死锁? 为什么会出现死锁? 怎么排查代码中出现了死锁? 作为技术人员(工程师),在面对问题的时候,可能需要的能力是

java死锁性能分析

java故障诊断案例分析: 死锁瓶颈(性能分析) 只要是java程序, 都可以使用这种方式来分析性能的瓶颈 1. Dump信息查看 Thread dump信息对于性能诊断非常有用 kill 命令使用 kill : 杀死一个进程 ? -9: 强制杀死一个进程 ? -3: 打印进程的Thread dump信息 linux系统: kill -3 pid windows: 在命令行窗口上, 按一个组合键: ctrl + break(fn+B键) 2. 死锁程序分析 java的一个死锁程序代码: publ

Java死锁排查和Java CPU 100% 排查的步骤整理(转)

工欲善其事,必先利其器 简介 本篇整理两个排查问题的简单技巧,一个是java死锁排查,这个一般在面试的时会问到,如果没有写多线程的话,实际中遇到的机会不多:第二个是java cpu 100%排查,这个实际的开发中,线的应用出现这个问题可能性比较大,所以这里简单总结介绍一下,对自己学习知识的一个整理,提高自己的解决问题能力. 一.Java死锁排查 通过标题我们就要思考三个问题: 什么是死锁? 为什么会出现死锁? 怎么排查代码中出现了死锁? 作为技术人员(工程师),在面对问题的时候,可能需要的能力是

什么是函数响应式编程(Java&amp;Android版本)

什么是函数响应式编程(Java&Android版本) 原文链接:http://www.bignerdranch.com/blog/what-is-functional-reactive-programming/ 函数响应式编程(FRP)为解决现代编程问题提供了全新的视角.一旦理解它,可以极大地简化你的项目,特别是处理嵌套回调的异步事件,复杂的列表过滤和变换,或者时间相关问题. 我将尽量跳过对函数响应式编程学院式的解释(网络上已经有很多),并重点从实用的角度帮你理解什么是函数响应式编程,以及工作中