当所有ExecutorService任务完成后,程序不会立即终止

36

我将一堆可运行对象放入了一个ExecutorService中:

// simplified content of main method
ExecutorService threadPool = Executors.newCachedThreadPool();
for(int i = 0; i < workerCount; i++) {
    threadPool.execute(new Worker());
}

我希望我的程序/进程在所有工人完成后立即停止。但根据我的日志,需要额外20-30秒才能完成。实际上,工人们并没有分配任何资源,事实上,此时他们什么都不做。

别误解我,这对我来说不是一个关键问题,我只是想了解发生了什么,我想知道这是否是正常的行为。

6个回答

42

Executors.newCachedThreadPool() 使用 Executors.defaultThreadFactory() 作为其 ThreadFactorydefaultThreadFactory 的 Javadoc 中表示"每个新线程都是一个非守护线程"(强调添加)。因此,为 newCachedThreadPool 创建的线程是非守护线程。这意味着它们会阻止JVM自然退出(通过"自然退出"我指的是您仍然可以调用System.exit(1)或终止程序来使JVM停止)。

应用程序能够完成的原因是newCachedThreadPool 内创建的每个线程都会在一段时间后超时并关闭自己。当其中的最后一个线程关闭时,如果您的应用程序没有剩余任何非守护线程,则它将退出。

您可以(而且应该)通过 shutdownshutdownNow 手动关闭 ExecutorService

另请参阅有关守护进程的JavaDoc的Thread


虽然这样做可以工作,但正确的做法是您必须关闭ExecutorService @alapeno。 - Gray
@Gray:不,看一下我在回答中引用的Javadoc - 如果它们闲置了60秒,那些线程会自动关闭。 - Russell Zahniser
@Gray:如果你所说的“核心线程”是指主线程,那么它会在main()函数结束时终止。如果你指的是AWT线程,在没有可显示组件时它会终止。此外,在ExecutorService中并没有后台线程在“运行”,它的线程池中除了这些线程之外没有其他线程;例如,在调用execute()方法的线程中直接在该方法中将任务排队。 - Russell Zahniser
线程池中核心线程下方的缓存线程(参见ThreadPoolExecutor)会自动关闭,但除非它们是守护线程,否则池中的核心线程永远不会自行关闭。如果已提交任何任务,则必须等待缓存线程超时并被回收。@RussellZahniser - Gray
@Gray:我觉得你忽略了一个事实,OP正在使用Executors.newCachedThreadPool(),它的核心大小为0。 - Russell Zahniser
显示剩余2条评论

8
当所有的工作线程运行结束后,我期望我的程序/进程立即停止。但根据我的日志记录,这需要额外的20-30秒才能完成。实际上,这些工人没有分配任何资源,目前什么也没做。问题在于您没有关闭您的ExecutorService。在将所有作业提交到服务之后,您应该关闭服务,否则除非其中所有线程都是守护线程,否则JVM将无法终止。如果您没有关闭线程池,则与ExecutorService相关联的任何线程(如果不是守护程序)都会阻止JVM停止。如果您已经向缓存的线程池提交了任何任务,则必须等待线程超时并被回收,然后JVM才能完成。
ExecutorService threadPool = Executors.newCachedThreadPool();
for(int i = 0; i < workerCount; i++) {
    threadPool.execute(new Worker());
}
// you _must_ do this after submitting all of your workers
threadPool.shutdown();

启动线程作为守护进程很可能不是您想要做的,因为在应用程序停止之前,您的应用程序可能会停止,此时所有任务将立即终止。我刚完成了一项快速审计,在我们的生产代码中使用178次ExecutorService类中,只有2个启动为守护线程。其余的都已正确关闭。
如果需要在应用程序退出时强制停止ExecutorService,则应使用带有线程中断标志的shutdownNow()并正确处理。

2
你对“必须”一词的定义是什么?ExecutorService的javadocs并没有说它“必须”被关闭(只是说它“可以”被关闭),而你断言它会挂起(不是“可能”挂起)要么是对OP问题的重申,要么就是不正确的,这取决于你如何定义“挂起”。我同意关闭ExecutorService是一个好习惯,但如果你要在这个问题上投反对票并且三次做出这种断言,坦白地说我认为你需要支持你的观点。 - yshavit
我修改了我的回答@yshavit以添加更多细节。尽管javadocs确实说它“可以”,但如果您不关闭它,则JVM将不会终止。我编写了数百个ExecutorService实现,很少将它们设置为非守护线程。在结束时关闭它们是正确的模式。 - Gray
好的,这已经足够成为最佳实践了,几乎是必须的。 - yshavit

5
基本上在ExecutorService上,您需要调用shutdown(),然后调用awaitTermination():
ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
   taskExecutor.execute(new MyTask());
}
taskExecutor.shutdown();
try {
  taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
  ...
}

4
Executors.newCachedThreadPool()javadoc中可以看到:

未被使用超过六十秒的线程会被终止并从缓存中移除。

如果您已知道不会再有新任务提交给ExecutorService,通常最好调用shutdown()。然后队列中的所有任务都将完成,但服务将立即关闭。
(或者,如果您不关心是否所有任务都已完成 - 例如,如果它们正在处理与主UI不相关的后台计算,则可以创建一个ThreadFactory,并将该池中的所有线程设置为后台线程。)

感谢您提示调用 shutdown(),我认为这对于我的场景是合适的。 - alapeno
在调用shutdown()之后,您还需要调用awaitTerminate(...)。Shutdown()不会等待先前提交的任务完成。另请参见https://dev59.com/PnM_5IYBdhLWcg3wvF3J - Danke Xie

0

针对ExecutorService的多线程问题,解决方案是 threadPool.shutdown();


0

这是由于组合 keepAliveTime=60L、timeunit=TimeUnit.SECONDS 和 corePoolSize=0* 导致的:当线程完成任务时,它不会立即终止,而是可能在 keepAliveTime 期间等待新任务。

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

*如果核心池大小不为0,请参考ThreadPoolExecutor的allowCoreThreadTimeOut()方法

**等待取决于池中当前运行线程的数量、corePoolSize和maximumPoolSize的组合


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