Java 定时器挂起问题

标签 java timer freeze

我一直在绞尽脑汁试图找出 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):

Eclipse IDE screenshot

当您调用 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 方法运行已注册的关闭 Hook
  • Thread-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/

相关文章:

java - 使用倒数计时器以特定时间间隔发出信号

c - pthread 根据用户输入中断线程循环

ios - 如何强制 iOS 应用程序挂起而不是崩溃?

java 。如何允许用户使用字符串输入跳过提示的数字要求?

java - iText image.setRotationDegrees() 不保持一致的原点

WPF:按钮单击+双击问题

list - Haskell:为什么将变量的尾部重新分配给它自己的变量会出现问题?

java - 如何调试使用 java 反射调用的代码?

java - XML 中的 backgroundTint 不会更改 BottomActionBar 的颜色,即使最小 API >= 21

Java计时器在您单击空格时启动,在再次单击空格后结束