等待所有 ExecutorService 线程完成

Await completion of all ExecutorService Threads

我试图掌握 Java 中的并发性,我编写了这个简单的代码来打印字母表中的字母:

public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
    
    final ExecutorService threadPool = Executors.newFixedThreadPool(4);
    final ExecutorCompletionService<Character> completionService = new ExecutorCompletionService<>(threadPool);

    final List<Character> letters = IntStream.range(65, 91).mapToObj(i -> (char) i).collect(Collectors.toList());

    for (char letter : letters) {
      completionService.submit(() -> printLetter(letter));
    }
    
    System.out.println("Starting shutdown");

    threadPool.shutdown(); // I WAS EXPECTING CODE TO STOP HERE, WAITING FOR ALL THREADS COMPLETION
    
    System.out.println("Ending shutdown");

  }

private static char printLetter(char letter) {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    System.out.println("Hello from: " + letter);
    return letter;
}

当我执行上面的代码时,我希望代码在 运行 "threadPool.shutdown()" 时等待前面线程的完成,但是它会继续执行其余代码,正如我在输出中看到的那样:

Starting shutdown
Ending shutdown
Hello from: B
Hello from: D
....
Hello from: Z

虽然我想要的输出是:

Starting shutdown
Hello from: B
Hello from: D
....
Hello from: Z
Ending shutdown

我尝试使用 threadPool.awaitTermination(30, TimeUnit.SECONDS) 但由于我忽略的原因,它等待 30 秒的完全完成在继续之前,即使所有字母都已打印。

如何等待所有线程完成?

When I executed the code above, I was expecting the code to await the completion of the previous threads when running "threadPool.shutdown()"

这不会发生,来自 ExecutorService 的文档:

void shutdown()

Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.

This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.

为了达到你的结果,你需要同时使用,shutdown()首先然后awaitTermination​():

boolean awaitTermination​(long timeout, TimeUnit unit) throws InterruptedException

Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.

returns: true if this executor terminated and false if the timeout elapsed before termination

此行为:

I tried using threadPool.awaitTermination(30, TimeUnit.SECONDS) instead but for a reason I ignore yet, it awaits the full completion of the 30 seconds before continuing even if all letters have been printed.

可能是由于 shutdown()替换为awaitTermination​()造成的。它正在等待“非关闭”池。如前所述,您需要在 awaitTermination​().

之前调用 shutdown()

是正确的,应该接受。这里还有一些想法。

不要把执行器服务当成线程池

你说:

I was expecting the code to await the completion of the previous threads when running "threadPool.shutdown()",

ExecutorService threadPool

这些表明您将执行程序服务视为线程池。你不应该。 Executors 框架的发明是为了减轻您管理线程的苦差事。

关于ExecutorService…“服务”这个词的意义在于它的工作在您不知道或不关心细节的情况下完成。另一个词“执行者”指的是它的工作是执行你的 Runnable/Callable 任务。没有提到“线程”是故意的。

所以在建立你的 ExecutorService 对象之后,你不应该再考虑线程池了。线程唯一关心的是你的任务跨线程访问资源。

随着作为 Project Loom 的一部分开发的 虚拟线程 的到来,这一点在未来会更加重要。

Loom 项目

工作正在 Project Loom to greatly enhance the concurrency facilities in Java. Experimental builds are available now, based on early-access Java 18 完成。

AutoCloseable

一个方便的增强功能是制作 ExecutorService AutoCloseable. This means we can use try-with-resources 语法。

当您分配的所有任务完成后,自动关闭处理执行程序服务及其支持线程池的关闭。您无需调用 shutdownawaitTermination 方法。

您的代码会更简单、更明显。

try (
    ExecutorService executorService = Executors.… ;
) {
    // Submit your `Runnable`/`Callable` tasks to the executor service.
    …
}
// At this point, flow-of-control blocks until all submitted tasks are done/canceled/failed.
// After this point, the executor service will have been automatically shutdown, wia `close` method called by try-with-resources syntax.