2013-07-23 48 views
2

我一直在挠头,试图找出Java定时器的挂起问题。我想知道这里有人可以帮忙。高度赞赏任何帮助诊断问题。Java定时器挂起问题

我有一个简单的程序,有三个TimerTask类(A,B和Stopper)。 A和B分别每400ms和500ms反复运行。定时器任务计划在2秒内运行以关闭所有内容。计时器按预期启动,并且run()方法的任务按预期执行。但是,一旦停止任务执行,我希望程序终止,但它只是在打印“所有任务和定时器被取消,退出”后才挂起。我已经使用jstack来诊断问题尝试,但并没有什么明显的指示什么,如果有什么需要发布/停止/取消等

这里是我的代码:

package com.example.experiments; 

import java.util.Date; 

/** 
* A test timer class to check behavior of exit/hang issues 
*/ 
public class TimerTest { 

    TimerTest(){ 
    } 

    class TaskA extends java.util.TimerTask { 

     TaskA(){ 
     } 
     public void run() { 
      System.err.println("A.run() called."); 

      if (!running){ 
       System.err.println("A: calling this.cancel()."); 
       this.cancel(); 
       return; 
      } 

     } 
     public boolean cancel(){ 
      System.err.println("Canceling TaskA"); 
      return super.cancel(); 
     } 
    } 

    class TaskB extends java.util.TimerTask { 

     TaskB(){ 
     } 

     public void run(){ 
      System.err.println("B.run() called."); 

      if (!running){ 
       System.err.println("B: calling this.cancel()."); 
       this.cancel(); 
       return; 
      } 

     } 
     public boolean cancel(){ 
      System.err.println("Canceling TaskB"); 
      return super.cancel(); 
     } 
    } 


    private void start(){ 
     this.running = true; // Flag to indicate if the server loop should continue running or not 

     final java.util.Timer timerA = new java.util.Timer(); 
     final TaskA taskA = new TaskA(); 
     timerA.schedule(taskA, 0, 400); 

     final java.util.Timer timerB = new java.util.Timer(); 
     final TaskB taskB = new TaskB(); 
     timerB.schedule(taskB, 0, 500); 

     class StopperTask extends java.util.TimerTask { 
      private java.util.Timer myTimer; 

      StopperTask(java.util.Timer timer){ 
       myTimer = timer; 
      } 

      public void run(){ 
       taskA.cancel(); 
       taskB.cancel(); 
       timerA.cancel(); 
       timerB.cancel(); 

       this.cancel(); 
       myTimer.cancel(); 
       System.err.println("Stopper task completed"); 
      } 
     } 
     final java.util.Timer stopperTimer = new java.util.Timer(); 
     final StopperTask stopperTask = new StopperTask(stopperTimer); 
     stopperTimer.schedule(stopperTask, 2*1000); 


     /** Register witjh JVM to be notified on when the JVM is about to exit */ 
     java.lang.Runtime.getRuntime().addShutdownHook(new Thread() { 
      @Override 
      public void run() { 
       System.err.println("shutting down..."); 
       running = false; 

       taskA.cancel(); 
       taskB.cancel(); 
       timerA.cancel(); 
       timerB.cancel(); 

       stopperTask.cancel(); 
       stopperTimer.cancel(); 

       System.err.println("All tasks and timers canceled, exiting"); 
       System.exit(0); 
      } 
     });  

    } 

    public static void main(String[] args) { 
     new TimerTest().start(); 
    } 

    private boolean running = false; 
} 
+0

只是一个好奇的评论,你为什么不导入所有的类,而是用全名引用它们?你显然有进口的能力。 – skiwi

+0

“一切都按预期进行,但程序只是在Stopper任务执行时挂起”,这句话令人困惑。阻止者任务总是在某个时间点发出(或)? – kosa

+0

添加条件以测试进程是否正在运行,然后取消它。查看它是否挂起。 – Phani

回答

0

而不是System.exit(0)执行返回。另外,你应该标记你的运行变量volatile。

+0

不应该System.exit(0)关闭JVM吗?你可以添加更多的澄清? – kosa

+0

@Karthik - 谢谢,那有效!但我不明白为什么。我可以要求你解释为什么,或指向一个链接? – PKK

+0

@PKK看到我的答案澄清 – linski

-1

“StopperTask.cancel()”hmmmmmmmmmmmmmm。 看看序列,它的调用之前退出不再存在!

+0

对不起,我不明白。你的意思是什么退出不存在? – PKK

5

由于Karthik回答,删除System.exit(0)和程序不会挂起。我也同意他关于volatile关键字的评论。

当关闭钩子正在运行时,JVM已处于其关闭序列中,并被“静态”监视器保护。当时调用System.exit(0)方法将有效地将JVM置于deadlock state中。

考虑下面的代码示例:

public static void main(String[] args) { 
    System.out.println(Thread.currentThread().getName()); 
    java.lang.Runtime.getRuntime().addShutdownHook(new Thread() { 
     @Override 
     public void run() { 
      System.out.println(Thread.currentThread().getName()); 
      System.exit(0);     
     } 
    }); 
} 

它也将挂起 - 红色方块按钮意味着该程序仍在运行,正如你可以在控制台选项卡中看到它打印出线程的名称运行该main方法(main)和线程的名字运行的关闭挂钩(Thread-0):

Eclipse IDE screenshot

当你调用System.exit方法,这将反过来被调用的方法是Shutdown.exit方法(I省略所有的不相干源):

static void exit(int status) { 

    ... 

    synchronized (Shutdown.class) { // "static" monitor mentioned in the first part of the post   
     sequence(); 
     halt(status); 
    } 
} 

sequence方法运行所有的钩子和终结,而halt方法调用本机halt0方法在这一点上,JVM最终会退出,我想。

因此,这是发生了什么:

  • main方法在main线程运行,它打印的线程名称并注册关闭挂钩
  • ,因为在它没有其他的代码,该main线程死亡
  • DestroyJavaVM线程开始执行JVM
  • DestroyJavaVM螺纹的关闭进入所述同步块在Shutdown.exit方法并取得Shutdown.class显示器
  • sequence方法运行已注册的关闭挂钩
  • Thread-0启动线程来运行我们在main方法注册的关闭挂钩
  • Thread-0线程打印其名称,并启动另一个JVM关闭经由System.exit方法,这反过来又试图获取Shutdown.class监视器但不能,因为它已经获得的

综上所述:

  • Thread-0线程DestroyJavaVM线程等待完成
  • Thread-0线程等待DestroyJavaVM线程完成

这是僵局的定义。

注:

  • 有关更多信息,我建议你阅读SO质疑How to capture System.exit event?
  • 系统的Java类的链接代码的OpenJDK 6〜B14,而我的是甲骨文1.6.0_37,但我在发现无差异资源。
  • 我认为Eclipse没有显示线程状态正确,Thread-0肯定应该在BLOCKED状态,因为它试图获取一个监视器(参见代码示例中的here)。不知道DestroyJavaVM线程,我不会假设没有做线程转储。
+1

+1。很好的答案。很好的了解关闭挂钩上的死锁。 – kosa

+0

@林斯基:真棒,感谢澄清! – PKK

+0

@PKK np,这让我很感兴趣。如果你觉得它有帮助,我可以建议你upvote答案? – linski