我一直在试图弄清楚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;
}
Run Code Online (Sandbox Code Playgroud)
正如Karthik回答的那样,删除System.exit(0)并且程序不会挂起.我也同意他关于volatile关键字的评论.
当正在运行关闭挂钩时,JVM已经处于其关闭序列中,该序列由"静态"监视器保护.此时调用该System.exit(0)方法将有效地使JVM处于死锁状态.
请考虑以下代码示例:
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);
}
});
}
Run Code Online (Sandbox Code Playgroud)
它也会挂起 - 红色方块按钮表示程序仍在运行,正如您在控制台选项卡中看到的那样,它打印出运行main方法(main)的线程的名称以及运行关闭钩子的线程的名称(Thread-0):

当你调用System.exit方法时,反过来调用的Shutdown.exit方法是方法(我省略了所有不相关的源):
static void exit(int status) {
...
synchronized (Shutdown.class) { // "static" monitor mentioned in the first part of the post
sequence();
halt(status);
}
}
Run Code Online (Sandbox Code Playgroud)
该sequence方法运行所有钩子和终结器,而我想这个halt方法调用halt0JVM最终退出的本机方法.
所以这就是:
main方法在main线程中运行,它打印线程名称并注册关闭钩子main线程就会死掉DestroyJavaVM线程开始执行JVM的关机DestroyJavaVM线程进入在上述同步块Shutdown.exit的方法和获取Shutdown.class监视器sequence方法运行已注册的关闭挂钩Thread-0线程开始运行我们在main方法中注册的shutdown hookThread-0线程打印其名称并通过该System.exit方法启动另一个JVM关闭,该方法又尝试获取 Shutdown.class监视器,但它不能,因为它已经被获取总结一下:
DestroyJavaVM线程等待Thread-0线程完成Thread-0线程等待DestroyJavaVM线程完成根据定义,这是死锁.
笔记:
Thread-0肯定应该处于BLOCKED状态(参见此处的代码示例).不确定DestroyJavaVM线程,我不会假设没有做一个线程转储.