CompletableFuture runAsync and supplyAsync

CompletableFuture runAsync and supplyAsync

1. Overview

In this article, we will discuss the runAsync and supplyAsync methods of the CompletableFuture interface. This CompletableFuture interface was added to the java.util.concurrent package in the Java 8 release.

2. CompletableFuture runAsync and supplyAsync

You can use the CompletableFuture interface for asynchronous programming. In other words, this interface runs the non-blocking code as a task in a non-blocking thread. After execution, it notifies the caller thread about the task progress, completion, or any failure.

This CompletableFuture has the following methods to execute the code:

1. supplyAsync

This method takes a Supplier<U> as an argument and returns the CompletableFuture<U> instance back to the caller thread. A supplier is nothing but a function that contains the code to be executed asynchronously. This function returns a value that you can retrieve later by using the CompletableFuture<U> instance.

2. RunAsync

This method accepts a Runnable as an input parameter and returns CompletableFuture<Void>. If you notice, the type of the CompletableFuture is void, meaning this method won’t return any result back to the caller.

We have seen the differences between the supplyAsync and runAsync. Now let’s understand each of these functions with examples.

2.1. CompletableFuture supplyAsync with common pool

The supplyAsync method uses the common pool if no executor is specified:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

The above supplyAsync uses the threads from the common pool (ForkJoinPool#commonPool) to execute the non-blocking code.

The following code uses the supplyAsync method which internally gets thread from the common pool. Also, the supplyAsync returns CompletableFuture<String> instance as the return type of supplier function is String.

public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println(ForkJoinPool.getCommonPoolParallelism());
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
            System.out.println("SupplyAsync : " +
                    Thread.currentThread().getName());
            return "Success";
        });
}

If you execute the above code, you can see in the console that the supplier code is executed by the worker thread of ForkJoinPool#commonPool.

SupplyAsync : ForkJoinPool.commonPool-worker-3

Note that the supplier method returns a string as output. You can retrieve this result string by using the callbacks available with CompletableFuture.

For example, the whenComplete method executes after the non-blocking supplier code completes its execution or throws any exception. You can retrieve the return value of the supplier method inside this function. Therefore, the below method prints the return value "Success".

cf.whenComplete((result, throwable) ->
                {
                    if (throwable == null) {
                        System.out.println("whenComplete : "
                                + result
                                + " in thread. " + Thread.currentThread().getName());
                    }
                });
/* prints whenComplete : Success in thread. ForkJoinPool.commonPool-worker-3 */

2.2. CompletableFuture supplyAsync with executor

The following supplyAsync takes both the supplier function and executor. It uses the threads specified in the executor to run the supplier (non-blocking) code. This method returns CompletableFuture<U> instance, meaning the supplier function returns value of type U.

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

In the below code, we are creating a fixed thread pool using the Executor. The executor is passed as an argument to the supplyAsync method. Also, the supplyAsync returns CompletableFuture<String> instance as the return type of supplier function is String.

ExecutorService e = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(),
                r -> new Thread(r, "supplyAsycPool"));
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
      System.out.println("supplyAsync executed in the thread of passed executor: " +
                Thread.currentThread().getName());
      return "Success";
}, e);

If you run the above code, then you can notice that the non-blocking supplier code is executed by the thread created in the executor.

supplyAsync executed in the thread of passed executor: supplyAsycPool

The supplyAsync method returns a string as output. You can retrieve this result string by using the callbacks available with CompletableFuture.

For example, the whenComplete method executes after the non-blocking supplier code completes its execution or throws any exception.

You can retrieve the return value of the supplier method inside this function. Therefore, the below method prints the return value "Success".

cf.whenComplete((result, throwable) ->
                {
                    if (throwable == null) {
                        System.out.println("whenComplete : "
                                + result
                                + " in thread. " + Thread.currentThread().getName());
                    }
                });
/* prints 
whenComplete : Success in thread. supplyAsycPool */

2.3. CompletableFuture runSync with common pool

The runSync accepts the runnable and uses the common thread pool (ForkJoinPool.commonPool) to execute it. This method returns CompletableFuture<Void> instance, meaning the supplier function does not return any value.

CompletableFuture<Void> runAsync(Runnable runnable)

For example, the following code uses the thread from the common pool.

CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.println("runAsync executed in the thread: " +
                    Thread.currentThread().getName());
        });

If you execute the above code, then you can notice that the runAsync supplier code ran using the common pool thread.

runAsync executed in the thread: ForkJoinPool.commonPool-worker-3

2.4. CompletableFuture runSync with executor

The following runSync accepts both the runnable and executor. It uses the threads of the Executor to execute the non-blocking runnable code. This method returns CompletableFuture<Void> instance, meaning the supplier function does not return any value.

CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

In the below code, we are creating a new thread pool using the Executors and passing it to the runAsync method.

ExecutorService e = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(),
                r -> new Thread(r, "runAsyncThread"));
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
     try {
         Thread.sleep(2000);
     } catch (InterruptedException ex) {
         ex.printStackTrace();
     }
     System.out.println("runAsync executed in the thread of passed executor: " +
            Thread.currentThread().getName());
}, e);

If you execute the above code, then you can notice that the runAsync supplier code ran using the created executor thread.

runAsync executed in the thread of passed executor: runAsyncThread

3. Conclusion

To sum up, we have learned the purpose of supplyAsync and runAsync methods available in the CompletableFuture along with examples. If you don’t want any output or result from the non-blocking code, then use runAsync. Otherwise, use supplyAsync method to execute your non-blocking code.

To learn more about Java, refer to our articles.

One thought on “CompletableFuture runAsync and supplyAsync

Leave a Reply

Your email address will not be published. Required fields are marked *