首页 文章

在守护程序线程未完成之前JVM不会退出

提问于
浏览
7

我只是偶然发现了守护程序线程的奇怪行为,我无法解释 . 我已将代码缩减为最小,完整且可验证的样本:

public static void main(String[] args) throws InterruptedException {

    Thread runner = new Thread(() -> {

        final int SIZE = 350_000;
        for (int i = 0; i < SIZE; i++) {
            for (int j = i + 1; j < SIZE; j++) {
                if (i*j == SIZE * SIZE - 1) {
                    return;
                }
            }
        }
    });

    runner.setDaemon(true);
    runner.start();

    // Thread.sleep(1000);
    System.out.println("Exiting.");
}

runner 线程执行的代码大约需要12秒才能在我的盒子上终止,我们对它的作用不感兴趣,因为我只需要花一些时间来计算 .

如果此片段按原样运行,它将按预期工作:它在启动后终止 . 如果我取消注释 Thread.sleep(1000) 行并运行程序,它可以工作大约12秒,然后输出"Exiting"并终止 .

据我所知,守护程序线程如何工作,我希望这段代码运行1秒然后终止执行,因为运行的唯一用户线程是使用main()方法启动的( runner 是后台守护程序线程)一旦1000毫秒通过,它就会到达执行结束,JVM应该停止 . 此外,看起来很奇怪"Exiting"仅在12秒后打印,而不是在程序启动时打印 .

我错了吗?如何实现所需的行为(暂停一秒然后停止,独立于跑步者线程正在做什么)?

我在Linux机器上使用64位Oracle JDK 1.8.0_112,如果从IDE或命令行启动它也具有相同的行为 .

谢谢,安德烈

2 回答

  • 2

    这可能是计数循环优化的结果,它从嵌套循环中删除了安全点轮询 . 尝试将 -XX:+UseCountedLoopSafepoint 标志添加到JVM启动选项中 .

  • 5

    Thread#sleep(long)在主线程从主方法返回之前暂停(即在JVM考虑完成程序之前,只要没有非deamon线程处于活动状态) . 然后,调度程序可以自由运行任何其他可运行的线程,该线程将是deamon线程 . 就目前而言,没有明显的理由让JVM在完成执行之前强行抢占deamon线程以继续在主线程中(它已经完成了休眠),因此JVM可以自由地继续其计划 . 但是,它可以随时选择暂停正在运行的线程并安排另一个可运行的线程执行,因此无法保证您的示例的可重现性 .

    您可以通过在循环中插入 Thread#yield()#sleep(1) 来强制执行抢占 . 我打赌你会开始看到片段退出的速度越快,然后才能完成循环 .

    有关线程状态和调度的更多信息,可以找到一个很好的概述here .

    Update 征求意见:

    我无法修改后台线程中的代码(是一个要求),所以我一直在寻找一种方法来阻止它,如果它需要太长时间(我正在做的描述是stackoverflow.com/questions/41226054 / ... ) .

    从法律上讲,只有stop a running thread from within才可能,所以你通常每次迭代测试中止条件,如果满足条件, run 方法 return; s . 中止条件可以像从外部设置的布尔标志一样简单(!volatile caveat!) . 因此,最简单的解决方案是让主线程在睡眠后设置这样的标志 .

    另一种可能是使用支持超时的 ExecutorService ,有关ScheduledExecutorService的示例,请参阅this问答 .

    我仍然不明白调度程序如何决定在运行System.out指令之前等待12秒 .

    它不等待12秒,它让's the deamon thread run to completion because being a deamon only matters to the JVM when deciding if it'安全地停止JVM . 对于调度程序,只有线程的状态很重要并且就其所关注的而言,在主线程的1s睡眠之后,它具有运行(deamon)和可运行的线程(main),并且没有指示正在运行的线程应该暂停支持runnable线程 . 切换线程在计算上也很昂贵,因此调度程序可能不愿意缺少任何指示 . 切换的指示可能是睡眠和产量,但也有GC运行和许多其他事情 .

相关问题