Mit94
Mit94

Reputation: 988

Await completion of all ExecutorService Threads

I'm trying to get a grasp on concurrency in Java, I've made this simple code that prints the letters of the alphabet :

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;
}

When I executed the code above, I was expecting the code to await the completion of the previous threads when running "threadPool.shutdown()", however it keeps executing the rest of the code as I can see in the output:

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

While the desired output for me would be:

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

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.

How can I await the completion of all threads?

Upvotes: 1

Views: 655

Answers (2)

Basil Bourque
Basil Bourque

Reputation: 339837

The Answer by MarcoLucidi is correct and should be accepted. Here are a few more thoughts.

Do not think of executor service as a thread pool

You said:

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

ExecutorService threadPool

These indicate that you think of the executor service as a thread pool. You should not. The Executors framework was invented to relieve you of the chore of managing threads.

Regarding ExecutorService… The point of the word “service” is that its job gets done without you knowing or caring about the details. And the other word “executor” refers to its job being the execution of your Runnable/Callable tasks. No mention of “thread” is deliberate.

So after establishing your ExecutorService object, you should no longer be thinking in terms of thread pools. The only concern for threads would be related to your tasks accessing resources across threads.

This point will be even more relevant in the future with the arrival of virtual threads being developed as part of Project Loom.

Project Loom

Work is being done in Project Loom to greatly enhance the concurrency facilities in Java. Experimental builds are available now, based on early-access Java 18.

AutoCloseable

One convenient enhancement is making ExecutorService AutoCloseable. This means we can use try-with-resources syntax.

When all your assigned tasks are complete, the automatic closing handles the shutting down of the executor service and its backing thread pool. No need for you to call the shutdown & awaitTermination methods.

Your code will be simpler and more obvious.

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.

Upvotes: 0

MarcoLucidi
MarcoLucidi

Reputation: 2177

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

this won't happen, from ExecutorService's docs:

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.

in order to achieve your result, you need to use both, shutdown() first and then 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

this behaviour:

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.

was probably caused by replacing shutdown() with awaitTermination​(). it was waiting on a "non-shutdown" pool. as already said, you need to call shutdown() before awaitTermination​().

Upvotes: 3

Related Questions