在ExecutorService上安排守护线程,解释为什么这是不好的形式。

21

我对使用ExecutorService调度的线程进行有序关闭的概念感到舒适; 也就是说,调用shutdownshutdownNow将导致在池中创建的线程优雅地退出。如果它们响应interrupt,您可以确保会调用finally等,并且您将获得干净且可预测的退出(可以清理任何资源等)。

但是,如果您已将线程设置为守护程序(通过执行器的ThreadFactory),如下所示:

ExecutorService pool = Executors.newSingleThreadExecutor(new ThreadFactory() {
   @Override
   public Thread newThread(Runnable runnable) {
      Thread thread = Executors.defaultThreadFactory().newThread(runnable);
      thread.setDaemon(true);
      return thread;
   }
});

在主线程终止后,虚拟机会突然终止任何守护线程。在上面的示例中,调度然后突然终止的(守护)线程将绕过任何finally块,并且任何可中断方法都不会抛出InterruptedException

因此,我倾向于认为将ThreadPoolExecutor池中使用的线程标记为守护线程是一个不好的做法... 我的问题实际上是帮助我表述为什么。

ExecutorService线程池中使用守护线程为什么是不好的做法(如果您不同意则不是)?特别地,我对描述具有优雅关闭(具有中断策略并且表现良好的线程)的 VM 关闭的生命周期,与守护线程相比,很感兴趣。

扩展上述最后一点,ThreadPoolExecutor上的finalize将在其自身上调用shutdown,但是当它使用守护线程时,如果VM调用了finalize,它们可能已经终止。那么线程池的行为是什么?如果基础线程突然终止,它能被欺骗为保持活动状态(从而不退出VM)吗?

我提出这个问题的部分原因是因为我曾看到它用于绕过需要关闭实际ExectorService的需求。您能想到绕过其关闭生命周期可能会产生不良影响的情况吗?到目前为止,我能想到使用守护线程的唯一原因是为了节省时间,我想知道它可能会引起的任何意外副作用。

3个回答

14
在 ExecutorService 的线程池中使用守护线程是否是不好的实践?
如果发送到该特定 ExecutorService 的任务可以突然结束,那么为什么不能使用守护线程呢?但一般来说,没有多少任务可以完全不经过关闭程序就立即终止,所以如果选择使用守护线程,则必须知道自己在做什么。
finalize() 方法在对象即将被垃圾回收时被调用。无法保证任何特定对象何时(如果有)被 GCd,而 ThreadPoolExecutor 也不例外,因此它的 finalize() 方法可能会被调用,也可能不会被调用。这种行为取决于特定的 JRE 实现,即使是相同的实现,也可能随时间而变化。

将线程池本身设置为始终使用守护线程,这肯定会限制其使用吧?这意味着任何被调度的线程都必须知道并接受它...因此,如果是用于框架代码,这可能会有所限制。 - Toby
@Toby:绝对是有限制的使用,因此不应该被用作一般解决方案。至少该特定池的入口处应该被强烈标记为警告标志。 - Joonas Pulakka

6
守护线程很有用,如果它们没有被突然终止,我认为它们就不会那么有用。我们可以想象另一种类型的线程,当没有正常线程在运行时中断它们,而不是突然终止它们。这可能有点方便,但如果您需要进行任何清理工作,则很可能希望进行有序的清理工作。这将限制此功能的便利性。另一方面,如果您有不需要在关闭时进行任何清理的任务,则守护线程非常方便。您不希望浪费时间等待它们到达某个特定状态或在关闭时出现挂起等问题,因为您使用守护线程的原因是因为您不需要任何类型的清理。如果应用程序正在关闭,执行任何操作都是浪费时间。如果您关心的话,那么您就不应该使用守护线程。守护线程池也是一样的道理。如果该线程池正在执行不需要在关闭时进行任何清理的任务,那么它就很有意义,因为很方便。来自JCiP book

守护线程应该谨慎使用,因为很少有处理活动可以在任何时候安全地放弃而不需要清理。特别是,对于可能执行任何类型的I/O任务来说,使用守护线程是很危险的。最好将守护线程留给“维护”任务,例如定期从内存缓存中删除过期条目的后台线程。


5

我倾向于为守护线程和非守护线程分别创建不同的线程池。守护线程池通常用于执行定期清理任务、监控和后台任务,即使其中一个或两个未被执行也不会有太大影响。任何只在应用程序运行时具有意义的任务都适合作为守护线程任务,例如GC线程就是守护线程。


1
当您可能以编程方式关闭行为良好的线程池时,为什么要允许这些线程以不干净的方式终止呢?也就是说,为什么不在杂项任务/清理线程池上调用shutdown而不将它们作为守护进程。 - Toby
1
因为这是开发人员需要知道的额外内容,但不会对结果产生任何影响,所以可能不会保持一致性。 - Peter Lawrey
嗯,我并没有看到那个...如果你不关闭执行器,虚拟机就不会退出。我认为开发人员已经意识到需要调用shutdown/Now了,所以为什么要绕过这个不需要关闭的特殊情况池呢?我希望人们都能调用shutdown来关闭它们两个...? - Toby
2
后台任务通常由不同于执行关闭操作的主要代码编写者编写的库执行。如果您有多个开发人员,则可能不知道如何关闭隐藏在库中的执行器。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接