我偶然发现了守护程序线程的奇怪行为,我无法解释。我已将代码缩减为最小,完整且可验证的样本:
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秒,然后打印出“退出”并终止。
据我所知,守护程序线程如何工作,我希望这段代码运行1秒然后终止执行,因为运行的唯一用户线程是使用main()方法启动的用户线程({{1} }是一个后台守护程序线程),一旦传递1000毫秒,它就会到达执行结束并且JVM应该停止。此外,看起来很奇怪“退出”仅在12秒后打印,而不是在程序启动时打印。
我错了吗?如何实现所需的行为(暂停一秒然后停止,独立于跑步者线程正在做什么)?
我在Linux机器上使用64位Oracle JDK 1.8.0_112,如果从IDE或命令行启动它也会有相同的行为。
谢谢, 安德烈
答案 0 :(得分:5)
这可能是计数循环优化的结果,它从嵌套循环中删除了安全点轮询。尝试将-XX:+UseCountedLoopSafepoint
标志添加到JVM启动选项中。
答案 1 :(得分:2)
Thread#sleep(long)
在主线程从主方法返回之前暂停(即在JVM考虑完成程序之前,只要没有非deamon线程处于活动状态)。然后,调度程序可以自由运行任何其他可运行的线程,该线程将是deamon线程。就目前而言,没有明显的理由让JVM在完成执行之前强行抢占deamon线程以继续在主线程中(它是否已经完成了休眠),因此JVM可以自由地继续它的调度。但是,它可以在任何时候选择暂停正在运行的线程并安排另一个可运行的线程执行,因此不能保证您的示例的可重复性。
您可以通过在循环中插入对Thread#yield()
或#sleep(1)
的调用来强制执行抢占。我打赌你会开始看到该片段在完成循环之前更快地退出。
有关线程状态和日程安排的更多信息,可以找到一个很好的概述here。
更新以征求意见:
我无法修改后台线程中的代码(这是一项要求),所以我一直在寻找一种方法来阻止它,如果它需要太长时间(我正在做的事情的描述是stackoverflow.com/questions / 41226054 / ...)。
从法律上讲,它只能stop a running thread from within,因此您通常会在每次迭代时测试中止条件,如果满足条件,run
方法return;
s 。中止条件可以像从外部设置的布尔标志一样简单(!volatile caveat!)。因此,最简单的解决方案是让主线程在睡眠后设置这样的标志。
另一种可能是使用支持超时的ExecutorService
,有关this的示例,请参阅ScheduledExecutorService
Q&amp; A。
我仍然不明白调度程序在运行System.out指令之前如何决定等待12秒。
它等待 12秒,它让deamon线程运行完成,因为作为deamon只对JVM在决定是否安全停止时很重要JVM。对于调度程序,只有线程的状态很重要,并且就其所关注的而言,在主线程的1s睡眠之后,它具有运行(deamon)和可运行的线程(main),并且没有指示应该暂停正在运行的线程以支持可运行的线程。切换线程在计算上也很昂贵,因此调度程序可能不愿意缺少任何指示。切换的指示可能是睡眠和产量,但也有GC运行和许多其他事情。