How is CompletableFuture different from a Future

Development background

When multi-threading is used to process tasks, it is often necessary to wait for the completion of tasks in a certain stage, and then start new asynchronous tasks according to the results of the stage.

This logic can be implemented using a Future, where the Future’s isDone or GET methods determine whether the asynchronous task is complete or get the result. The problem with this approach is that isDone polling consumes CPU resources and does not provide timely completion status. The get method blocks the calling thread from processing subsequent logic.

CompletableFuture provides a callback mechanism and a rich Stream API, which can do a good job of callback and subsequent task processing of asynchronous tasks. And CompletableFuture can well describe the relationship between each asynchronous task, making the logic more clear and intuitive. The internal lockless concurrency stack can also perform parallel calls between asynchronous tasks with high performance.

Contrast, for example,

Simulation scenario:

Xiao Bai went to a restaurant and ordered a tomato egg and a bowl of rice. After the king wait for dinner. Now the waiter starts to cook the rice and the cook starts to fry the vegetables. After doing it, the waiter will serve the food and call xiao Bai to eat.

The Future implementation

     @Test
    public void FutureExample2(a) throws ExecutionException, InterruptedException {
        ExecutorService waiters = Executors.newFixedThreadPool(2);
        ExecutorService cookers = Executors.newFixedThreadPool(2);
        printTimeAndThread("Little White enters the dining room.");
        printTimeAndThread("White ordered scrambled eggs with tomato and a bowl of rice.");

        Future<String> makeRice = waiters.submit(() -> {
            ThreadTools.sleepSeconds(2);
            printTimeAndThread("Dinner's ready.");
            return "Rice";
        });

        Future<String> cookDish = cookers.submit(() -> {
            ThreadTools.sleepSeconds(2);
            printTimeAndThread("The dishes are ready.");
            return "Scrambled eggs with tomatoes.";
        });

        Future<String> getRice = waiters.submit(() -> {
            final long start = System.currentTimeMillis();
            String rice = makeRice.get();
            String dish = cookDish.get();
            ThreadTools. sleepSeconds(2);
            printTimeAndThread(String.format("When the waiter finishes cooking, it takes % D", System.currentTimeMillis() - start));
            return String.format("%s + %s okay", dish, rice);
        });
        printTimeAndThread("Little white is beating the king.");
        printTimeAndThread(String.format("%s, let's eat it.", getRice.get()));
    }
Copy the code

The results of

1634092703854 | main | small white enter restaurant 1634092703854 | main | white dots Tomato scrambled eggs + a bowl of rice 1634092703856 | main | 1634092705874 | small white play the king The pool - 2-1634092705874 | | dishes thread - 1 - thread pool - 1-1 dinner ready | 1634092707884 | | - thread pool - 1-2 waiter dozen rice, 4028 | 1634092707885 main | tomato scrambled eggs + rice good, small white eatCopy the code

CompletableFuture implementation

    @Test
    public void CPExample2(a) {
        printTimeAndThread("Little White enters the dining room.");
        printTimeAndThread("White ordered scrambled eggs with tomato and a bowl of rice.");

        CompletableFuture<String> makeRice = CompletableFuture.supplyAsync(() -> {
            sleepSeconds(300);
            FutureTaskTest.printTimeAndThread("Make rice");
            return "Rice";
        });
        CompletableFuture<String> makeDish = CompletableFuture.supplyAsync(() -> {
            sleepSeconds(200);
            printTimeAndThread("The cook cooks.");
            return "Scrambled eggs with tomatoes.";
        });
        final CompletableFuture<String> allDone = makeDish.thenCombine(makeRice, (dish, rice) -> {
            final long start = System.currentTimeMillis();
            sleepSeconds(200);
            printTimeAndThread(String.format("When the waiter finishes cooking, it takes % D", System.currentTimeMillis() - start));
            return String.format("%s + %s okay", dish, rice);
        });
        printTimeAndThread("Little white is beating the king.");
        printTimeAndThread(String.format("%s, let's eat it.", allDone.join()));
    }
/ / the code references from: https://gitee.com/phui/share-concurrent/tree/master
Copy the code

The results of

1634092882975 | main | small white enter restaurant 1634092882975 | main | white dots Tomato scrambled eggs + a bowl of rice 1634092882981 | main | 1634092885005 | small white play the king ForkJoinPool.com monPool worker - 2-1634092886001 | | chef cooking ForkJoinPool.com monPool - worker - 1 1634092888010 | | do rice ForkJoinPool.com monPool | waiter - worker - 1 dozen rice, 2009 1634092888011 | main | tomato scrambled eggs + rice good, small white eatCopy the code

The difference between

  1. CompletableFuture automatically calls subsequent tasks after the first two tasks are completed; The Future blocks a thread, waiting for the first two tasks to complete.
  2. The specified thread pool that the Future needs to display, and the CompletableFuture can use ForkJoinPool by default.
  3. CompletableFuture can use a stream-like API to represent a logical relationship between tasks, whereas a Future can’t represent this relationship syntactically.
  4. The CompletableFuture join method that gets the result throws a RuntimeException, while the GET method throws InterruptedException, ExecutionException

The basic use of CompletableFuture

Create an asynchronous task

CompletableFuture provides two apis for starting an asynchronous task. These are the runAsync method, which uses a Runnable interface object as a parameter, and the supplyAsync method, which uses a Supplier interface object pair as a parameter. The difference between the two apis is that the Runnable interface differs from the Supplier interface in that the runAsync method does not return a value, whereas the supplyAsync method does.

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) 
Copy the code

Both methods use the default ForkJoinPool as a thread pool to perform asynchronous tasks. If you need to specify additional thread pools, you can use the following overloaded API:

public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
Copy the code

Examples:

    @Test
    public void CompletableFutureStart(a) {
        CompletableFuture.runAsync(() -> {
            ThreadTools.sleepSeconds(1);
            System.out.printf("run on %s %n", Thread.currentThread().getName());
        });
        CompletableFuture.supplyAsync(() -> {
            ThreadTools.sleepSeconds(2);
            System.out.printf("run on %s %n", Thread.currentThread().getName());
            return "success";
        });
        ThreadTools.sleepSeconds(5);
    }
Copy the code

Execution Result:

run on ForkJoinPool.commonPool-worker-1 
run on ForkJoinPool.commonPool-worker-2 
Copy the code

Obtaining task results

CompletableFuture offers two more ways to get results than futures: getNow and Join.

public T    get(a)
public T    get(long timeout, TimeUnit unit)
public T    getNow(T valueIfAbsent)
public T    join(a)
Copy the code
  1. GetNow is similar to the option.orelse () method in Stream in Java8, which returns a given result if it does not exist or is not completed.
  2. A JOIN is similar to a GET in that it blocks the thread, except that it throws different exceptions. Join may throw an unchecked exception, and you don’t need to enforce a try catch. Get declares checked exceptions in the method signature and must be handled at code time.

Using an example

@Test
public void CompletableFutureGet(a) {
    try {
        final Void unused = CompletableFuture.runAsync(() -> {
            ThreadTools.sleepSeconds(1);
            System.out.printf("run on %s %n", Thread.currentThread().getName());
        }).get();
        System.out.println("CompletableFuture task 1 result "+unused);
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    
    final String result = CompletableFuture.supplyAsync(() -> {
        ThreadTools.sleepSeconds(2);
        System.out.printf("run on %s %n", Thread.currentThread().getName());
        return "success";
    }).join();
    System.out.println("CompletableFuture task2 result "+result);
    ThreadTools.sleepSeconds(5);
}
Copy the code

The results of

run on ForkJoinPool.commonPool-worker-1 
CompletableFuture task 1 result null
run on ForkJoinPool.commonPool-worker-1 
CompletableFuture task2 result success
Copy the code

Callbacks that depend on the pre-task

thenApplyXXX

Action: When the current phase (the CompletionStage object that calls this method) is not exception completed, the given function is executed with the result of the current phase as an input parameter, and the function returns a value.

public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);
Copy the code

thenAcceptXXXX

What it does: Similar to thenApply. When the current phase completes without exception, the given consumer function is executed, taking the current result as an input and returning no value. Method returns the CompletionStage object, whose stage result is null.

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
 public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
 public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
Copy the code

thenRunXXX

Role: Perform the defined action when the current phase is not abnormally completed. There are no input arguments and no return values.

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
Copy the code

whenCompleteXXX

Function: When the current phase completes, the given BiConsumer function is called with the thrown exception (null when it normally ends) and the result (null when it ends). The function returns no value, and the method returns a CompletionStage object containing the same result or exception as the current phase (without changing the result of the previous phase).

Features: Can handle both normal and abnormal situations.

public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,Executor executor);
Copy the code

handleXXX

When the current phase is complete, the given BiFunction function is called with the input arguments thrown (null when the exception ends) and the result (null when the exception ends), and the function returns a value. The CompletionStage object returned is the result wrapper for the functional interface.

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);
Copy the code

thenComposeXXX

Function: Executes the given function when the current phase is not exception completed. The function takes the result of the current stage as an input parameter and returns another CompletionStage object. ThenCompose method expands this object to avoid nesting of results. The effect is similar to flatMap in Stream.

public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,Executor executor);
Copy the code

For example:

  	/** * thenCompose example */
    @Test
    public void FutureTaskTestCompose(a) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "task 1");
        final CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "next stage");
        CompletableFuture<String> nextFuture = future1.thenCompose((result) -> {
            System.out.printf("future1 result is \"%s\"%n", result);
            return future2;
        });
        System.out.println(future2 == nextFuture);
        final String join = nextFuture.join();
        System.out.printf("nextFuture result is \"%s\"%n", join);
    }

    /** * thenCompose compares thenApply */
    @Test
    public void FutureTaskTestCompose2(a) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "task 1");
        final CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "next stage");
        //thenApply returns a CompletableFuture object
        final CompletableFuture<CompletableFuture<String>> nextFuture = future1.thenApply((result) -> {
            System.out.printf("future1 result is \"%s\"%n", result);
            return future2;
        });
        final CompletableFuture<String> innerStage = nextFuture.join();// The result is the CompletableFuture object
        final String result = innerStage.join();// Gets the result of the execution of the returned CompletableFuture object
        System.out.printf("nextFuture result is \"%s\"%n", result);
    }
Copy the code

ThenCompose results in output

future1 result is "task 1"
false
nextFuture result is "next stage"
Copy the code

ThenApply results output

future1 result is "task 1"
nextFuture result is "next stage"
Copy the code

Callbacks that depend on both tasks

Sometimes a subsequent task may need to rely on the results of the first two tasks, and CompletableFuture provides an API for that as well. It can be divided into two types, namely, the first two tasks are completed, and the subsequent task execution logic is invoked after either of the first two tasks is completed. Details are as follows:

After completing the first two tasks

thenAcceptBothXXX

Action: Similar to thenAccep, but dependent on two stages. When the execution of both the current phase and the given phase is complete, the given function is executed, taking the results of the two phases as input, and the function returns no value. The value of the result field in the CompletableFuture object is null.

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);
Copy the code

runAfterBothXXX

Function: Similar to thenRun, the given function is executed when the current phase and the given phase both complete with exceptions. Function has no input parameter and no return value. The value of the result field in the CompletableFuture object is null.

public CompletionStage<Void> runAfterBoth(CompletionStage
        other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage
        other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage
        other,Runnable action,Executor executor);
Copy the code

thenCombineXXX

The BiFunction function is executed when both the pre-task of calling the method and the pre-task of the given point are complete. The input parameter of the ** function is the result of the first two phases. The function has no return value. The value of the result field in the CompletableFuture object is the result calculated by the function.

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync (CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync (CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn,Executor executor);
Copy the code

Pre – completion of any task

applyToEitherXXX

Function: executes the given function on the completion of any non-exception of the current phase or given phase. ** Takes the result of the completion phase as an input parameter to the function, which has a return value. The value of the result field in the CompletableFuture object is the result calculated by the function.

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor xecutor);
Copy the code

acceptEitherXXX

Function: same as applyToEither However, the function is a consumer interface with no return value.

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);
Copy the code

runAfterEitherXXX

Function: same as applyToEither But the function is a Runnable interface, no input parameters, no return value.

public CompletionStage<Void> runAfterEither(CompletionStage
        other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage
        other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage
        other,Runnable action,Executor executor);
Copy the code

For example,

/** * an example of an API that relies on two pre-tasks */
@Test
public void bothOrAny(a) {

    final CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
        ThreadTools.sleepSeconds(1);
        return "task 1";
    });
    final CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
        ThreadTools.sleepSeconds(2);
        return "task 2";
    });
     // Depends on both pre-tasks being completed
    final CompletableFuture<Void> both = task2.thenAcceptBothAsync(task1, (r1, r2) -> {
        System.out.printf("Both missions have been accomplished. Result 1: %s, result 2: %s%n", r1, r2);
    });
    // Rely on two pre-tasks, but only one of them can be completed
    final CompletableFuture<Void> any = task1.acceptEitherAsync(task2, (r) -> {
        System.out.printf("One task has been completed. Result: %s%n", r);
    });
    // The main thread is blocked
    any.runAfterBoth(both, ()->{}).join();
}
Copy the code

The output

If one task is completed, the result is: Task 1 Both tasks are completed. Result 1: Task 2, Result 2: Task 1Copy the code

Callbacks that depend on multiple tasks

In addition to apis that rely on one or two pre-tasks, judgment based on batch task completion timing is more commonly used. CompletableFuture also provides a corresponding API.

When either one is done –anyOf

public static CompletableFuture<Object> anyOf(CompletableFuture
       ... cfs)
Copy the code

CompletableFuture provides static methods to represent the stage when any one of the leading tasks is complete (including exceptions). The other operations of the CompletableFuture can be performed after this stage.

After all missions are completed –allOf

public static CompletableFuture<Void> allOf(CompletableFuture
       ... cfs)
Copy the code

Similar to anyOf, the allOf static method represents a phase in which all incoming pre-tasks have been completed. The other operations of the CompletableFuture can be performed after this stage.

** Note: ** If an asynchronous task executes abnormally, it is considered complete.

For example,

@Test
public void anyANdAll(a) {
    final ExecutorService threadPool = Executors.newFixedThreadPool(10);
    final long startAll = System.currentTimeMillis();
    final CompletableFuture[] completableFutures = IntStream.rangeClosed(1.10).mapToObj(index -> CompletableFuture.supplyAsync(() -> {
        final long start = System.currentTimeMillis();
        try {
            Thread.sleep(100 * index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Thread '%s' completes execution with %d milliseconds %n", Thread.currentThread().getName(), System.currentTimeMillis() - start);
        return index;
    }, threadPool)).toArray(CompletableFuture[]::new);
    final Object result = CompletableFuture.anyOf(completableFutures).join();
    System.out.printf("Any of get result: %s, total cost %d milliseconds %n", result, System.currentTimeMillis() - startAll);
    CompletableFuture.allOf(completableFutures).join();// block main thread
    System.out.printf("All of get result, total time %d milliseconds %n", System.currentTimeMillis() - startAll);
}
Copy the code

The output

The thread 'pool-1-thread-1' completes execution, taking 110 milliseconds. 1, It took 122 milliseconds for the 'pool-1 thread-2' thread to complete, 205 milliseconds for the 'pool-1 thread-3' thread to complete, and 300 milliseconds for the 'pool-1 thread-4' thread to complete, and 411 milliseconds for the 'pool-1 thread-4' thread to complete Thread 'pool-1-thread-5' completes in 505 milliseconds thread 'pool-1-thread-6' completes in 615 milliseconds thread 'pool-1-thread-7' completes in 710 milliseconds Thread '- the thread pool - 1-8 completes, spend 804 milliseconds threads' - the thread pool - 1-9 completes, spend 915 milliseconds threads' - the thread pool - 1-10 completes, spend 1011 milliseconds all of results, which cost 1024 millisecondsCopy the code

Exception handling

The API for handling exceptions is similar to other callback apis, but the timing is different.

exceptionally

Function: When the current phase is abnormal, execute the given function with the exception as the input parameter. When the non-exception execution of the current phase is complete, no logic is executed and the method returns the same non-exception return value as the current phase.

public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);
Copy the code

For example,

@Test
public void CPExceptional(a) {
    CompletableFuture.<String>supplyAsync(() -> {
        throw new RuntimeException("Exception thrown");
    }).exceptionally((e) -> {
        System.out.printf("Exception message %s%n", e.getMessage());
        return "new result";
    }).thenAccept(r -> {
        System.out.printf("Final result 1:%s%n", r);
    });

    CompletableFuture.<String>supplyAsync(() -> "No exception is thrown.")
            .exceptionally((e) -> {
                System.out.printf("Exception message %s%n", e.getMessage());
                return "new result";
            }).thenAccept(r ->
            System.out.printf("Final result 2:%s%n", r));
}
Copy the code

The output

The exception message Java. Lang. RuntimeException: the exception thrown Results 1: the new result is the final result 2: no exception is thrownCopy the code

Classification and summary of API

Three basic task types

CompletableFuture provides multiple apis for each task execution time. These apis can correspond to functional interfaces for interface parameters. Runable: This API takes the Runable functional interface, which has no input parameters and no return value. So this is an API that only matters if the pre-task is completed. Corresponds to the xxxRun method in CompletableFuture.

Consumer: This API takes the Consumer functional interface as an argument, which has an input parameter and no return value. So this is an API that cares about the result of the lead task, but only consumes the lead result. Corresponds to the xxxAccept method in CompletableFuture.

Function: This API takes the Function interface as an argument, which has an input parameter and a return value. So this is an API that can manipulate the results of the pre-task. The xxxApply method in the corresponding CompletableFuture.

Three overloaded methods

In the CompletableFuture API, which describes task relationships, most methods of the same function provide three overloaded methods. Similar to: XXX, xxxAsync, xxxAsync(… Executor Executor). The differences between the three methods are as follows:

XXX method, indicating that the current task is not actively submitted to the thread pool for execution.

XxxAsync method, indicating that the task needs to be executed asynchronously in the default Fork/Join thread pool.

xxxAsync(… Executor Executor) method, indicating that tasks need to be executed asynchronously in the specified thread pool.

  @Test
    public void thenApplyRunTime(a) {
        System.out.println("Pre-completion test");
        final CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.printf("Pre-task running thread: %s%n", Thread.currentThread().getName());
        });
        ThreadTools.sleepSeconds(1);
        voidCompletableFuture.thenRun(() -> System.out.printf("ThenRun thread: %s%n", Thread.currentThread().getName())).join();

        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");

        System.out.println("Pre-completion Condition test");
        CompletableFuture.runAsync(() -> {
            System.out.printf("Pre-task running thread: %s%n", Thread.currentThread().getName());
            ThreadTools.sleepSeconds(1);
        }).thenRun(() -> System.out.printf("ThenRun thread: %s%n", Thread.currentThread().getName()))
                .thenRunAsync(()->System.out.printf("ThenRunAsync running thread: %s%n", Thread.currentThread().getName()))
                .join();

        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
        System.out.println("The lead task is not completed and there are other tasks in the thread pool.");
        final CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
            ThreadTools.sleepSeconds(2);
            System.out.printf("Pre-task running thread: %s%n", Thread.currentThread().getName());
        });
        voidCompletableFuture1.thenRun(() ->{ ThreadTools.sleepMillis(100); System.out.printf("ThenRun thread: %s%n", Thread.currentThread().getName()); }); voidCompletableFuture1.thenRun(() ->{ ThreadTools.sleepMillis(100); System.out.printf("ThenRun thread: %s%n", Thread.currentThread().getName()); }); voidCompletableFuture1.thenRun(() ->{ ThreadTools.sleepMillis(100); System.out.printf("ThenRun thread: %s%n", Thread.currentThread().getName()); }); voidCompletableFuture1.thenRun(() ->{ ThreadTools.sleepMillis(100); System.out.printf("ThenRun thread: %s%n", Thread.currentThread().getName()); }); voidCompletableFuture1.thenRun(() ->{ ThreadTools.sleepMillis(100); System.out.printf("ThenRun thread: %s%n", Thread.currentThread().getName()); }); voidCompletableFuture1.thenRunAsync(()->System.out.printf("ThenRunAsync running thread: %s%n", Thread.currentThread().getName()))
                .join();
          ThreadTools.sleepSeconds(10);
    }
Copy the code

Results output

Running thread: ForkJoinPool.com monpool-worker-9 thenRun Running thread: Main -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - front task without complete testing front task running thread: ForkJoinPool.com monPool - worker - 9 thenRun running threads. ForkJoinPool.com monpool-worker-9 thenRunAsync running thread: ForkJoinPool.com monPool - worker - 9 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - front task has been finished, at the same time there are other tasks in the thread pool Front task running thread: ForkJoinPool.com monpool-worker-2 thenRunAsync running thread: ForkJoinPool.com monpool-worker-9 thenRun running thread: ForkJoinPool.com monpool-worker-2 thenRun thread: ForkJoinPool.com monpool-worker-9 thenRun thread: ForkJoinPool.com monpool-worker-2 thenRun thread: ForkJoinPool.com monpool-worker-9 thenRun thread: ForkJoinPool.commonPool-worker-2 Process finished with exit code 0Copy the code

classification

CompletableFuture’s API can be categorized in three dimensions:

  1. Describes the task relationships
  2. Basic types of tasks
  3. An overloaded method of the API.

The three classification dimensions, combined with each other, make up most of the API in CompletableFuture.