Difference between Future and CompletableFuture

Reference article

Take you to understand Future and CompletableFuture

Thread

Please refer to the thread thread implementation method

Disadvantage : Unable to get the running result.

Future + Callable

Starting from Java 1.5, Callable and Future are provided, through which the task execution result can be obtained after the thread task is executed.

  1. boolean isDone();// Determine whether it has been completed.
  2. get(); // Block the main thread until the end of the child thread, and return the running result.
  3. get(long timeout, TimeUnit unit); // Block the main thread until the end of the child thread, and return the result; or timeout, an error will be reported.
 public static void test_1() {
    
    
        try {
    
    
            ExecutorService executor = Executors.newCachedThreadPool();
            Future<Integer> result = executor.submit(() -> {
    
    
                System.out.println("线程开始");
                Thread.sleep(30000);
                System.out.println("线程结束");
                return new Random().nextInt();
            });
            System.out.println("主线程-1");
            Thread.sleep(10000);
            System.out.println("主线程-2");

            //shutdown调用后,不可以再submit新的task,已经submit的将继续执行。
            executor.shutdown();

            Thread.sleep(10000);
            System.out.println("主线程-3");

            System.out.println("result:" + result.get());
            System.out.println("主线程-4");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

operation result:

主线程-1
线程开始
主线程-2
主线程-3
线程结束
result:-1240584785
主线程-4

From the results, you can see:

  1. ExecutorService.submit is executed immediately after the new child thread is submitted
  2. After ExecutorService.shutdown is called, no new task can be submitted, otherwise an error will be reported. Those already submitted will continue to be executed.
  3. Future.get() blocks the main thread and waits for the child thread to get the result before continuing to the main thread

Disadvantages : It is difficult for Futures to directly express the dependencies between the results of multiple Futures, such as merging two asynchronous calculations into one.

CompletableFuture

The CompletableFuture class implements the CompletionStage and Future interfaces, so you can use its isDone(), get(), get(long timeout, TimeUnit unit) and other methods like Future.

CompletableFuture retains the advantages of Future and makes up for its shortcomings. That is, after the asynchronous task is completed, there is no need to wait when you need to use the result to continue the operation. You can directly pass the result of the previous asynchronous processing to another asynchronous event processing thread through thenAccept, thenApply, thenCompose, etc. for processing.

Usage of CompletableFuture

  1. Create asynchronous operations, runAsync (return value is not supported) and supplyAsync method (return value is supported)

  2. The functions of whenComplete, handle, thenApply, and thenAccept are similar. They all continue to execute the next task after executing the thread of the current task.

    // whenComplete,handle 接受上一步的结果,和异常。
    // whenComplete 没有返回值,不影响join的结果,handle 有返回值,影响join的结果
    
    CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
    <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
    
    // thenAccept,thenApply 只接受上一步的结果,如果有异常则不会进入,可以和exceptionally搭配捕获异常
    // thenAccept 没有返回值,不影响join的结果,thenApply有返回值,影响join的结果
    
    CompletableFuture<Void> thenAccept(Consumer<? super T> action)
    <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn) 
    
  3. exceptionally: When the current task is abnormal, the callback method in exceptionally is executed.

  4. ThenRun method, after executing the thread of the current task, continue to execute the next task without caring about the processing result of the task.

  5. ThenCombine merges the tasks, thenCombine will execute the tasks of the two CompletionStage, and then hand the results of the two tasks to thenCombine for processing.

  6. The thenCompose method, the thenCompose method allows you to pipeline two CompletionStages. When the first operation is completed, the result is passed to the second operation as a parameter.

Async methods are asynchronous methods

The methods at the end of Async can all be executed asynchronously. If the thread pool is specified, it will be executed in the specified thread pool. If not specified, it will be executed in ForkJoinPool.commonPool() by default.

runAsync method: It takes the Runnabel functional interface type as a parameter, so the calculation result of CompletableFuture is empty.

public static CompletableFuture<Void> runAsync(Runnable runnable)
// executor 线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

The supplyAsync method takes the Supplier functional interface type as a parameter, and the calculation result type of CompletableFuture is U.

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
// executor 线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

thenApply transform result

The input of these methods is the result of the calculation in the previous stage, and the return value is the result after conversion

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

Examples:

private static void test_3() {
    
    
    System.out.println("主线程 1");
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    
    
        System.out.println("CompletableFuture step1 - start ");
        try {
    
    
            Thread.sleep(2000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("CompletableFuture step1 - end ");
        return "CompletableFuture step1";
    }).thenApplyAsync(v -> {
    
    
        System.out.println("CompletableFuture step2 - start ");
        try {
    
    
            Thread.sleep(2000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("CompletableFuture step2 - end ");
        return v + " -> CompletableFuture step2";
    });

    System.out.println("主线程 2");
    System.out.println(future.join());
    System.out.println("主线程 3");
}

operation result:

主线程 1
CompletableFuture step1 - start 
主线程 2
CompletableFuture step1 - end 
CompletableFuture step2 - start 
CompletableFuture step2 - end 
CompletableFuture step1 -> CompletableFuture step2
主线程 3

Result analysis:

  1. supplyAsync and thenApplyAsync are all running in the child thread without blocking the main thread.
  2. thenApplyAsync accepts the calculation result of the previous stage, and the return value is the result after conversion
  3. CompletableFuture.join() does not need to catch exceptions. Remarks: CompletableFuture.get() try-catch package to catch exceptions.
  4. CompletableFuture.join blocks the main thread from running

Note: In fact, the above code is a synchronous code, so there is no need to write thenApply.

thenAccept consumption result

这些方法的输入是上一个阶段计算后的结果,消费这些结果,没有返回值

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

Examples:

private static void test_4() {
    
    
    System.out.println("主线程 1");
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    
    
        System.out.println("CompletableFuture step1 - start ");
        try {
    
    
            Thread.sleep(2000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("CompletableFuture step1 - end ");
        return "CompletableFuture step1";
    }).thenAccept(v -> {
    
    
        System.out.println("CompletableFuture step2 - start ");
        try {
    
    
            Thread.sleep(2000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("CompletableFuture step2 - end ");
    });

    System.out.println("主线程 2");
    System.out.println(future.join());
    System.out.println("主线程 3");
}

operation result:

主线程 1
CompletableFuture step1 - start 
主线程 2
CompletableFuture step1 - end 
CompletableFuture step2 - start 
CompletableFuture step2 - end 
null
主线程 3

Result analysis: thenAccept and thenApply have similar functions, except that thenAccept has no return value, thenApply has return value

thenCombine mixes the results of two CompletionStage (2 threads)

henCombine mixes the results of two CompletionStage (2 threads) and returns after conversion

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)

Example:

private static void test_5() {
    
    
    System.out.println("主线程 1");
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    
    
        System.out.println("CompletableFuture step1 - start ");
        try {
    
    
            Thread.sleep(2000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("CompletableFuture step1 - end ");
        return "CompletableFuture step1";
    }).thenCombineAsync(CompletableFuture.supplyAsync(() -> {
    
    
        System.out.println("CompletableFuture step2 - start ");
        try {
    
    
            Thread.sleep(2000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("CompletableFuture step2- end ");
        return "CompletableFuture step2";
    }), (s1, s2) -> {
    
    
        return s1 + " " + s2;
    });

    System.out.println("主线程 2");
    System.out.println(future.join());
    System.out.println("主线程 3");
}

operation result:

主线程 1
CompletableFuture step1 - start 
CompletableFuture step2 - start 
主线程 2
CompletableFuture step1 - end 
CompletableFuture step2- end 
CompletableFuture step1 CompletableFuture step2
主线程 3

Result analysis:

  1. CompletableFuture step1 and CompletableFuture step1 are both running in the child thread at the same time. It can be seen that thenCombine supports asynchrony.
  2. thenCombineAsync supports the merging of two asynchronous calculations into one, which can solve the problem of Future

thenAcceptBoth and thenCombine have similar functions, both can merge asynchronous calculation results, but thenCombine has a return value, thenAcceptBoth has no return value

exceptionally catch exception

private static void test_6() {
    
    
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    
    
        System.out.println("a - start ");
        try {
    
    
            Thread.sleep(2000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("a - end ");
        return 100 / 0;
    }).exceptionally(e -> {
    
    
        System.out.println("a - exception ");
        e.printStackTrace();
        return 0;
    });
    System.out.println("result=" + future.join());
}

operation result:

a - start 
a - end 
a - exception 
result=0
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero......

Guess you like

Origin blog.csdn.net/fangye1/article/details/112851857