如何正确关闭 java ExecutorService

我有一个简单的java,运行一些任务对象(实现)。ExecutorServiceCallable

ExecutorService exec = Executors.newSingleThreadExecutor();
List<CallableTask> tasks = new ArrayList<>();
// ... create some tasks
for (CallableTask task : tasks) {
 Future future = exec.submit(task);
 result = (String) future.get(timeout, TimeUnit.SECONDS);
 // TASKS load some classes and invoke their methods (they may create additional threads)
 // ... catch interruptions and timeouts
}
exec.shutdownNow();

完成所有任务(完成或超时)后,我尝试关闭执行程序,但它不会停止:我怀疑某些超时的任务未正确终止。exec.isTerminated() = FALSE.

是的,我知道遗嘱执行人的关闭并不能保证任何事情:

除了尽最大努力停止处理主动执行的任务之外,没有其他保证。例如,典型的实现将通过 {@link Thread#interrupt} 取消,因此任何无法响应中断的任务都可能永远不会终止。

我的问题是,有没有办法确保这些(任务)线程将终止?我想出的最好的解决方案是在我的程序结束时调用,但这显然是愚蠢的。System.exit()


答案 1

来自ExecutorService的Oracle API文档页面的推荐方式:

 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }

如果池需要更多时间关闭,可以更改

if (!pool.awaitTermination(60, TimeUnit.SECONDS))

while (!pool.awaitTermination(60, TimeUnit.SECONDS))

关机相关方法的简要总结

关机()

启动有序关机,其中将执行以前提交的任务,但不接受任何新任务。

关机现在()

尝试停止所有正在执行的任务,暂停等待任务的处理,并返回等待执行的任务的列表。

awaitTermination(long timeout, TimeUnit unit) throw interruptedException

阻塞,直到所有任务在关闭请求后完成执行,或者发生超时,或者当前线程中断,以先发生者为准。


答案 2

您可以控制这些任务吗?也就是说,您是自己创建这些吗?我怀疑在这些地方的某个地方,线程中断被忽略了,例如

try {
  ....
}
catch {InterruptedException e) {
   // do nothing
}

当抛出中断异常时,线程上的中断标志需要重置,否则线程将不会退出。有关详细信息,请参阅此处

不幸的是,您可能正在使用一个不遵守此条款的库,在这种情况下,您无法轻易规避这一点。在这种情况下,一个重量级的选项是分叉出一个子进程来执行 的工作,这将在进程退出时清除所有资源。重量级,可能并非微不足道,但可靠。Callable