【问题标题】:Java Timer hang issueJava Timer 挂起问题
【发布时间】:2013-07-23 00:35:06
【问题描述】:

我一直在摸索,试图找出 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;
}

【问题讨论】:

  • 只是出于好奇而发表评论,您为什么不导入所有类而是用它们的全名引用它们?显然,您似乎拥有导入的能力。
  • “一切都按预期进行,但是当 Stopper 任务执行时程序只是挂起”,这句话令人困惑。停止任务总是在某个时间点发出(或)?
  • 添加条件来测试进程是否正在运行,然后取消它..看看它是否挂起。
  • @skiwi: 真的没有理由,从某人编写的一些现有代码中粘贴
  • @Nambari: 澄清描述,如果还不清楚请告诉我

标签: java timer freeze


【解决方案1】:

正如 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 方法 (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);
    }
}

sequence 方法运行所有挂钩和终结器,而 halt 方法调用本机 halt0 方法,此时 JVM 最终退出,我想。

这就是发生的事情:

  • main 方法在main 线程中运行,它打印线程名称并注册关闭钩子
  • 由于其中没有其他代码,main 线程死亡
  • DestroyJavaVM线程启动执行JVM的关闭
  • DestroyJavaVM线程进入Shutdown.exit方法中的同步块,获取Shutdown.class监视器
  • sequence 方法运行已注册的关闭挂钩
  • Thread-0 线程开始运行我们在main 方法中注册的关闭钩子
  • 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 线程,我不会假设不进行线程转储。

【讨论】:

  • +1。不错的答案。很高兴了解关闭挂钩上的死锁。
  • @PKK np,这让我很感兴趣。如果您觉得它有帮助,我可以建议您为答案投票吗?
【解决方案2】:

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

【讨论】:

  • 不应该 System.exit(0) 关闭 JVM 吗?你能补充更多的说明吗?
  • @Karthik - 谢谢,这行得通!但我不明白为什么。我可以请您解释原因或指向链接吗?
  • @PKK 请参阅我的回答以进行澄清
猜你喜欢
  • 1970-01-01
  • 2012-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多