我一直在绞尽脑汁试图找出 Java 定时器的挂起问题。我想知道这里是否有人可以帮忙。非常感谢任何帮助诊断问题的帮助。
我有一个包含三个 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;
}
最佳答案
正如 Karthik 回答的那样,删除 System.exit(0)
程序将不会挂起。我也同意他关于 volatile
的评论。关键词。
当运行关闭 Hook 时,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
) 和运行关闭钩子(Hook)的线程的名称(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);
}
}
sequence
方法运行所有 Hook 和终结器,而 halt
方法调用 native halt0
我想 JVM 最终退出的方法。
这就是发生的事情:
main
方法在main
线程中运行,它打印线程名称并注册关闭钩子(Hook)- 由于其中没有其他代码,
主
线程结束 - 启动
DestroyJavaVM
线程执行JVM的关闭 DestroyJavaVM
线程在Shutdown.exit
方法中进入synchronized block ,获取Shutdown.class
监听器sequence
方法运行已注册的关闭 HookThread-0
线程开始运行我们在main
方法中注册的关闭钩子(Hook)Thread-0
线程打印其名称并通过System.exit
方法启动另一个 JVM 关闭,该方法依次尝试获取Shutdown.class
监控但它不能,因为它已经被获取
总结:
DestroyJavaVM
线程等待Thread-0
线程完成Thread-0
线程等待DestroyJavaVM
线程完成
这就是死锁的定义。
注意事项:
- 有关其他信息,我建议阅读 SO 问题 How to capture System.exit event?
- 系统 java 类的链接代码是 openjdk 6-b14,而我的是 oracle 1.6.0_37,但我发现源代码没有区别。
- 我认为 Eclipse 没有正确显示线程状态,
Thread-0
绝对应该在 BLOCKED 中状态,因为它试图获取一个被占用的监视器(有关代码示例,请参见 here)。不确定DestroyJavaVM
线程,如果不进行线程转储,我不会假设。
关于Java 定时器挂起问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17818686/