我一直在试图弄清楚Java Timers的问题。我想知道这里是否有人可以提供帮助。任何诊断问题的帮助都非常感谢。
我有一个带有三个TimerTask类(A,B和Stopper)的简单程序。 A和B分别每400ms和500ms重复运行一次。 Stopper任务计划在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 :(得分: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
方法的线程的名称({{1} })以及运行关闭挂钩(main
)的线程的名称:
当您调用System.exit
方法时,将调用的方法是Shutdown.exit
方法(我省略了所有不相关的来源):
Thread-0
sequence
方法运行所有挂钩和终结器,而halt
方法调用JVM最终退出的本地halt0
方法,我猜想。
所以这就是:
static void exit(int status) {
...
synchronized (Shutdown.class) { // "static" monitor mentioned in the first part of the post
sequence();
halt(status);
}
}
方法在main
线程中运行,它打印线程名称并注册关闭钩子main
线程会死main
线程以执行JVM的关闭DestroyJavaVM
主题在DestroyJavaVM
方法中进入同步块并获取Shutdown.exit
监视器Shutdown.class
方法运行已注册的关闭挂钩sequence
线程以运行在Thread-0
方法中注册的关闭挂钩main
线程打印其名称并通过Thread-0
方法启动另一个JVM关闭,而System.exit
方法又尝试获取Shutdown.class
监视器,但它不能,因为它已经被获取总结一下:
DestroyJavaVM
线程等待Thread-0
线程完成Thread-0
线程等待DestroyJavaVM
线程完成根据定义是死锁。
注意:
Thread-0
肯定应该处于BLOCKED状态,因为它试图获取一个被采用的监视器(参见here代码示例) 。不确定DestroyJavaVM
线程,我不会假设没有进行线程转储。答案 1 :(得分:0)
而不是System.exit(0)
执行返回。此外,您应该将运行变量标记为volatile。
答案 2 :(得分:-1)
“StopperTask.cancel()”hmmmmmmmmmmmmmm。 查看序列,退出之前不再存在退出!