调度在 ExecutorService 上的守护线程;解释为什么这是不好的形式

发布于 11-30 16:50 字数 1227 浏览 0 评论 0原文

我对使用 ExectuorService 调度的线程有序关闭的想法感到满意;也就是说,调用 shutdownshutdownNow 将导致池中创建的线程正常退出。如果它们响应中断,您可以确定最终等将被调用,并且您将得到一个干净的、可预测的退出(您可以在其中清理任何资源等)。

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

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

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

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

为什么在 ExecutorService 的线程池中使用守护线程是不好的做法(如果您不同意,则不是)?我特别感兴趣的是描述虚拟机关闭的生命周期,正常关闭(具有中断策略并且运行良好的线程)与守护线程。

扩展最后一点,finalizeThreadPoolExecutor 上的 shutdown 会自行调用 shutdown,但当它使用守护线程时,如果 VM 调用 finalize,它们可能已经终止。那么线程池的行为是什么呢?如果底层线程突然终止,它是否会被欺骗以保持活动状态(因此不退出虚拟机)?

我问的部分原因是因为我看到它用来绕过关闭实际 ExectorService 的需要。您能想到绕过其关闭生命周期可能产生不良影响的场景吗?到目前为止,我能想到的使用守护进程的唯一原因是走捷径,我想了解它可能导致的任何意外副作用。

I'm comfortable with the idea of orderly shutdown on threads scheduled with an ExectuorService; that is to say, calling shutdown or shutdownNow will cause threads created on the pool to exit gracefully. If they respond to interrupt you can be sure finally etc will be called and you'll get a clean, predictable exit (where you can cleanup any resources etc).

However, if you've set your thread to be a daemon (via the executor's ThreadFactory) as below.

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

after the main thread terminates, the VM will abruptly terminate any daemon threads. In the example above, a (daemon) thread scheduled and then abruptly terminated will bypass any finally blocks and any interruptable methods won't throw InterruptedException.

So, I tend to think that marking the threads used in a ThreadPoolExecutor's pool as daemon is bad practice... my question is really about helping me vocalise why.

Why is it bad practice (or not if you disagree) to use daemon threads in a ExecutorService's thread pool? In particular I'm interested in describing the life-cycle of the VM shutdown with graceful shutdown (threads that have an interruption policy and play nicely) vs daemon threads.

Expanding that last point, finalize on ThreadPoolExecutor will call shutdown on itself, but when it's using daemon threads, they could have terminated already if finalize was called by the VM. What's the behavior of the thread pool then? Can it be tricked into remaining alive (and so not exiting the VM) if underlying threads terminated abruptly?

Part of the reason I'm asking is because i've seen it used to bypass the need to shutdown the actual ExectorService. Can you think of scenarios where bypassing its shutdown life-cycle can have ill affect? So far, the only reason I can come up with for using daemons is to take a short cut and I want to appreciate any unexpected side affects it could cause.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

狂之美人2024-12-07 16:50:41

在 ExecutorService 的线程池中使用守护线程是一种不好的做法吗?

如果发送到特定 ExecutorService 的任务可以突然终止,那么为什么不呢,这就是守护线程所做的。但一般来说,没有多少任务可以在没有关闭仪式的情况下终止,因此如果您选择守护线程,您必须知道自己在做什么。

当对象即将被垃圾回收时,finalize() 被调用。无法保证任何特定对象何时(如果有)将被 GCd,ThreadPoolExecutor 也不例外,因此它的 Finalize() 可能会也可能不会被调用。该行为取决于特定的 JRE 实现,即使使用相同的实现,也可能会不时发生变化。

is it bad practice to use daemon threads in a ExecutorService's thread pool?

If the tasks sent to that particular ExecutorService are ok to be terminated abruptly, then why not, that's what daemon threads do. But generally, there are not many tasks that are ok to be terminated with no shutdown ceremonies at all, so you must know what you're doing if you opt to daemon threads.

finalize() is called when an object is about to be garbage collected. There are no guarantees on when, if ever, any particular object will be GCd, and ThreadPoolExecutor is no exception, so its finalize() may or may not be called. The behavior depends on the particular JRE implementation, and even with the same implementation, may vary from time to time.

顾北清歌寒2024-12-07 16:50:41

守护线程可能很有用,如果它们没有突然终止,在我看来它们就不会那么有用。

想必我们可以想象另一种类型的线程,当不再有正常线程运行时,该线程会被中断,而不是突然终止。这可能有点方便,但如果您必须进行任何清理,那么您可能想要进行有序清理。这会限制该功能的便利性。

另一方面,如果您有不需要在关闭时进行任何清理的任务,则守护线程非常方便。而且您不想浪费时间等待它们到达某个特定状态或冒关闭时挂起的风险等,因为您使用守护线程的原因是因为您不需要任何类型的清理。如果应用程序执行任何操作都会浪费时间。正在关闭。如果您关心,那么您不应该使用守护线程。

这与守护线程池没有什么不同。如果该线程池正在执行在关闭时不需要任何清理的任务,那么由于方便而有意义。

来自 JCiP 书籍

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

Daemon threads can be useful, and if they weren't terminated abruptly, they wouldn't be so useful IMO.

Presumably we could imagine another type of thread, which are interrupted when no normal threads are running anymore, instead of being abruptly terminated. That may be a little convenient, but if you had to do any clean up at all, it's likely that you wanted to do an orderly clean up. This would limit the convenience of this feature.

On the other hand, if you had tasks that would not need any clean up at shutdown, deamon threads are quite convenient. And you wouldn't want to waste time waiting them to arrive at some specific state or risk a hang up on shutdown etc., because the reason you are using a deamon thread is because you don't need any kind of clean up. It would be a waste of time to execute anything if the app. is shutting down. If you care, then you shouldn't have used deamon threads.

It's no different with deamon thread pools. If that thread pool is doing tasks that do not need any clean up at shutdown, then it would make sense because of the convenience.

From the JCiP book:

Daemon threads should be used sparinglyfew processing activities can be safely abandoned at any time with no cleanup. In particular, it is dangerous to use daemon threads for tasks that might perform any sort of I/O. Daemon threads are best saved for "housekeeping" tasks, such as a background thread that periodically removes expired entries from an in-memory cache

浪菊怪哟2024-12-07 16:50:41

我倾向于为守护线程和非守护线程使用不同的池。守护进程池往往会执行重复的清理作业、监视和后台任务,如果其中一两个任务未执行,这些任务也无关紧要。任何仅在应用程序仍在运行时才有意义的任务都适合创建守护线程任务。例如,GC 线程是守护线程。

I tend to have different pools for daemon and non-daemon threads. Daemon pools tend to do recurring clean up jobs, monitoring and background tasks which don't matter if one or two is not executed. Any task which is only meaningful while the application is still running is good to make a daemon thread task. e.g. GC threads are daemon threads.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文