简体   繁体   中英

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?

ForkJoinPool uses daemon threads that does not prevent JVM from exiting. On the other hand the threads in the ExecutorService created by Executors are non-daemon threads, hence it keeps JVM from exiting until you explicitly shutdown the thread pool.

Also notice that in your example you need to shutdown the pool at the end in order to terminate the JVM.

executorService.shutdown();

So, one solution would be to keep the main thread waiting for few seconds until your computation is completed like so,

Thread.sleep(4000);

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM