Java Timer挂起问题

时间:2013-07-23 18:53:11

标签: java timer hang

我一直在试图弄清楚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;
}

3 个答案:

答案 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)的线程的名称:

Eclipse IDE screenshot

当您调用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线程完成

根据定义是死锁。

注意:

  • 如需了解更多信息,我建议您阅读问题How to capture System.exit event?
  • 系统java类的链接代码是openjdk 6-b14,而我的是oracle 1.6.0_37,但我注意到源代码没有区别。
  • 我认为Eclipse没有显示正确的线程状态,Thread-0肯定应该处于BLOCKED状态,因为它试图获取一个被采用的监视器(参见here代码示例) 。不确定DestroyJavaVM线程,我不会假设没有进行线程转储。

答案 1 :(得分:0)

而不是System.exit(0)执行返回。此外,您应该将运行变量标记为volatile。

答案 2 :(得分:-1)

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