Search code examples
javamultithreadingthreadpoolcompletable-futureforkjoinpool

Behaviour of ForkJoinPool in CompletableFuture.supplyAsync()


I'm comparing the behaviour of CompletableFuture.supplyAsync() in the two cases in which I set a custom ExecutorService or I want my Supplier to be executed by the default executor (if not specified) which is ForkJoinPool.commonPool()

Let's see the difference:

public class MainApplication {
  public static void main(final String[] args) throws ExecutionException, InterruptedException {

    Supplier<String> action1 = () -> {
        try {
            Thread.sleep(3000);
        }finally {
            return "Done";
        }
    };
    Function<String, String> action2 = (input) -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            return input + "!!";
        }
    };

    final ExecutorService executorService = Executors.newFixedThreadPool(4);

    CompletableFuture.supplyAsync(action1, executorService)
                     .thenApply  (action2)
                     .thenAccept (res -> System.out.println(res));

    System.out.println("This is the end of the execution");

  }
}

In this case I'm passing executorService to my supplyAsync() and it prints:

This is the end of the execution

Done!!

So "Done" gets printed after the end of the main execution.

BUT if I use instead:

CompletableFuture.supplyAsync(action1)

so I don't pass my custom executorService and the CompletableFuture class uses under the hood the ForkJoinPool.commonPool() then "Done" is not printed at all:

This is the end of the execution

Process finished with exit code 0

Why?


Solution

  • In both cases when you do

    CompletableFuture.supplyAsync(action1, executorService)
                         .thenApply  (action2)
                         .thenAccept (res -> System.out.println(res));
    

    you don't wait for task completition. But then you program is going to exit and there is differences how common fork join pool:

    ForkJoinPool.commonPool()
    

    and regular executor service:

    final ExecutorService executorService = Executors.newFixedThreadPool(4);
    

    ..react on attempt to call System.exit(...) equivalent.

    This is what doc says about fork join common pool, you should point attention to that:

    However this pool and any ongoing processing are automatically terminated upon program System.exit(int). Any program that relies on asynchronous task processing to complete before program termination should invoke commonPool().awaitQuiescence, before exit.

    That is link to ExecutorService docs, you may point attention to:

    The shutdown() method will allow previously submitted tasks to execute before terminating

    I think that may be a difference you asking about.