使用ThreadMXBean类来编程检测死锁

摘要:所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。那么在写java程序的时候,死锁是否要检测到呢,答案是肯定的。
  

所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。那么在写java程序的时候,死锁是否要检测到呢,答案是肯定的。

死锁产生的条件:   

〈1〉互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如独木桥就是一种独占资源,两方的人不能同时过桥。

    〈2〉不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。

    〈3〉占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。

    〈4〉循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。

  上面我们提到的这四个条件在死锁时会同时发生。也就是说,只要有一个必要条件不满足,则死锁就可以排除。

在编程中检测死锁


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

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

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 )

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

原文:http://www.youxijishu.com/blog/myBlog/2/12

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值