CompletableFuture asynchronous task orchestration usage and detailed explanation

At work, multiple services or methods are often called to obtain different data. The traditional approach is to obtain them one by one serially, and then encapsulate and return them. We can try to use CompletableFuture to hand over multiple operations to asynchronous threads for execution, and then the main thread waits for the longest task to complete and returns all the results together.

FutureLimitations

When we get the Future containing the result, we can use the get method to wait for the thread to complete and get the return value, but we all know that future.get() is a blocking method and will wait until the thread completes execution to get the return value. We can see that the get method in FutureTask is to loop the code until the thread completes execution and returns.

  /**
     * Awaits completion or aborts on interrupt or timeout.
     *
     * @param timed true if use timed waits
     * @param nanos time to wait, if timed
     * @return state upon completion
     */
    private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    
    
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
    
    
        	//循环 省略代码
        	...
        }

Let's consider a scenario. If we need to get the return value after executing the asynchronous task 1, and then use the return value to execute other asynchronous call 2, then we need to block in the main thread and wait for the asynchronous task 1 to complete, and then hand over the asynchronous execution Task 2, and then continues to block waiting for the return of task 2. This not only blocks the main thread , but also has poor performance .

Introduction to CompletableFuture

What is CompletableFuture: CompletableFuture combines the advantages of Future and provides very powerful extended functions of Future, which can help us simplify the complexity of asynchronous programming, provide functional programming capabilities, process calculation results through callbacks, and provide Methods for converting and combining CompletableFutures.

Functional programming : Use Functional Interface as a parameter, which can be implemented using lambda expression suggestions. Programming is convenient and has been explained before.

Commonly used auxiliary methods

  1. isCompletedExceptionally : Whether the CompletableFuture ends abnormally.
// 包括取消cancel、显示调用completeExceptionally、中断。
public boolean isCompletedExceptionally() {
    
    
        Object r;
        return ((r = result) instanceof AltResult) && r != NIL;
    }
  1. isCancelled : Whether the CompletableFuture is canceled before normal execution is completed.
 public boolean isCancelled() {
    
    
        Object r;
        // 判断异常是否是CancellationException
        return ((r = result) instanceof AltResult) &&
            (((AltResult)r).ex instanceof CancellationException);
 }
  1. isDone : Whether the CompletableFuture has been executed, including exception generation and cancellation.
 public boolean isDone() {
    
    
       return result != null;
 }
  1. get : blocking to obtain the CompletableFuture result
 public T get() throws InterruptedException, ExecutionException {
    
    
        Object r;
        return reportGet((r = result) == null ? waitingGet(true) : r);
 }
  1. join : blocking to obtain CompletableFuture results
 public T join() {
    
    
        Object r;
        return reportJoin((r = result) == null ? waitingGet(false) : r);
 }

The difference between join() and get() is that join() returns the calculated result or throws an unchecked exception (CompletionException), while get() returns a specific exception.

CompletableFutureBuild

CompletableFuture has a parameterless constructor. At this time, an unfinished CompletableFuture is created. Using get will always block the main thread.

So we generally use static methods to create instances.

// 无入参有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
// 无入参无返回值,简单的执行
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);

We note that each method has a refactored method. The Executor parameter can manually specify the thread pool, otherwise the default is ForkJoinPool.commonPool() system-level public thread pool.


[Note]: These threads of the default commonPool are daemon threads. We need to use daemon threads carefully when programming. If we set our ordinary user thread as a daemon thread, when the main thread of our program ends and there are no other user threads in the JVM, then the daemon thread of CompletableFuture will exit directly, causing the task Unable to complete the problem! !

Common APIs for CompletableFuture

The APIs we will explain later generally have three similar methods. I will only demonstrate the third type.

  1. xxx(method);
  2. xxxAsync(method);
  3. The difference between the three types of xxxAsync(method, executor)
    is that the first type of synchronous execution is executed by the main thread, the second type of asynchronous execution is executed by the default thread pool, and the third type of asynchronous execution is executed by the created thread pool.

1. Build a CompletableFuture instance

Now try using CompletableFuture. Start with a basic build.

/**
* 1.run + runAsync + supply + supplyAsync
*/
/** 1.异步运行交由默认线程池(forkjoinpool)无入参无返回值run
*   2.异步运行交由创建线程池(threadPoolExecutor)无入参无返回值run
*/
CompletableFuture<Void> run = CompletableFuture.runAsync(() ->
        System.out.println("completablefuture runs asynchronously"));

CompletableFuture<Void> runCustomize = CompletableFuture.runAsync(() ->
        System.out.println("completablefuture runs asynchronously in customize threadPool"
        ), threadPoolExecutor);


//================下面是supply===================
/** 1.异步运行交由默认线程池(forkjoinpool)无入参有返回值supply
*   2.异步运行交由自己创建的线程池 无入参有返回值supply
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously");
    return "success";
});

CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success";
}, threadPoolExecutor);

result

completablefuture runs asynchronously
completablefuture runs asynchronously in customize threadPool
completablefuture supplys asynchronously
completablefuture supplys asynchronously in customize threadPool

The first two types of CompletableFuture have no value, so when you want to use them in subsequent chain calls, the input parameters are null.

2.whenComplete

Consider that when we end the execution of CompletableFuture, we hope to get the execution result or exception, and then further process the result or exception. Then we need to use whenComplete.

  /**
  * 入参是BiConsumer,第一个参数是上一步结果、第二个是上一步执行的异常
  */
 CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
          int a = 1/0;
          System.out.println("completablefuture supplys asynchronously");
          return "success";
 });
 CompletableFuture<String> complete = supply.whenCompleteAsync((result, throwable) -> {
    
    
     System.out.println("whenComplete: " + result + " throws " + throwable);
 });

result:

whenComplete: null throws java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero

3.handle

The input parameters of handle and whenComplete are the same, but it can return the execution result after the execution is completed , while whenComplete can only process and cannot return.

public <U> CompletableFuture<U>     handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U>     handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U>     handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

example:

/**
* 入参是BiFunction 第一个参数是上一步结果、第二个是上一步执行的异常 
* 返回值可以是任何类型
*/
CompletableFuture<String> handleAsyncCustomize = supply.handleAsync((a, throwable) -> {
    
    
            System.out.println("handleAsyncCustomize: " + a + " throws " + throwable);
            return "handleAsync success";
}, threadPoolExecutor);

System.out.println(handleAsyncCustomize.get());

result:

handleAsyncCustomize: success throws null
handleAsync success

4.thenApply

thenApply is very similar to handle. It has input parameters and return values, but it only has one input parameter and cannot handle the exception of the previous asynchronous task. If an abnormal get occurs, an error will be reported.

public <U> CompletableFuture<U>     thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U>     thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U>     thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
/**
* Function 入参是第一个异步任务执行结果、出参是返回值
* 若异步任务1异常 则2无法执行 get会报错
*/
 CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
     System.out.println("completablefuture supplys asynchronously in customize threadPool");
     int a = 1/0;
     return "success";
 }, threadPoolExecutor);

 CompletableFuture<String> applyAsyncCustomize = supplyCustomize.thenApplyAsync(a -> {
    
    
     System.out.println("applyAsyncCustomize " + a);
     return "success";
 }, threadPoolExecutor);
 System.out.println(applyAsyncCustomize.get());

result:

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)
	at CompletableFutureTask.main.main(main.java:48)
Caused by: java.lang.ArithmeticException: / by zero
	at CompletableFutureTask.main.lambda$main$3(main.java:40)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

5.thenAccept

thenAccept has input parameters but no return value. If the chain call continues, the next asynchronous task will get a null value.

public CompletableFuture<Void>  thenAccept(Consumer<? super T> action)
public CompletableFuture<Void>  thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void>  thenAcceptAsync(Consumer<? super T> action, Executor executor)

example:

/**
*	Consumer第一个入参是上一步返回结果,若没返回结果则为null
*	无返回值
*/
CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
   System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success";
}, threadPoolExecutor);

CompletableFuture<Void> applyAsyncCustomize = supplyCustomize.thenAcceptAsync(a ->{
    
    
    System.out.println("accept " + a);
}, threadPoolExecutor);
System.out.println(applyAsyncCustomize.get());

result:

completablefuture supplys asynchronously in customize threadPool
accept success
null

6.thenCompose

It is different from thenCombine. thenCombine combines two CompletableFuture return results for asynchronous processing, while thenCompose encapsulates a new CompletableFuture return based on the first return result.

example:

CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
     System.out.println("completablefuture supplys asynchronously");
     return "success";
 });
 CompletableFuture<String> future = supply.thenComposeAsync(a -> {
    
    
     String name = a + " or fail";
     return CompletableFuture.supplyAsync(() -> {
    
    
         return name;
     });
 });
 System.out.println(future.get());

result:

completablefuture supplys asynchronously in customize threadPool
success or fail

7.exceptionally

When handling an exception, note that the return value type after the exception needs to be consistent with the CF return value type where the exception occurred, which is equivalent to a service downgrade idea.

example:

/**
*	发生异常时候,返回0
*/
CompletableFuture<Integer> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
     int a = 1/0;
     System.out.println("completablefuture supplys asynchronously in customize threadPool");
     return 2;
 }, threadPoolExecutor).exceptionally(a->{
    
    
     System.out.println(a);
     return 0;
 });

 System.out.println(supplyCustomize.get());

result:

completablefuture runs asynchronously in customize threadPool
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
0

CompletableFuture combined API-all tasks are completed

1.thenCombine

thenCombine is a combined task. The CompletableFuture used above is completed in a chain. When the first one is completed, the next asynchronous call is made based on the first execution result. However, combined asynchrony can make two asynchronous tasks completely independent. Only Execution will continue when they are all completed.

example:

/**
*	BiFunction入参,两个CF的返回结果作为入参,有返回值
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
	System.out.println("completablefuture supplys asynchronously");
    return "success";
});
// 异步运行交由默认线程池(forkjoinpool)无入参有返回值supply
CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success";
}, threadPoolExecutor);

CompletableFuture<String> future = supply.thenCombine(supplyCustomize, (a, b) -> {
    
    
    return "thenCombine result: " + a + "-" + b;
});
System.out.println(future.get());

result:

completablefuture supplys asynchronously
completablefuture supplys asynchronously in customize threadPool
thenCombine result: success-success

2.thenAcceptBoth

The difference between thenAcceptBoth and thenCombine is that there is no return value. The two CF return values ​​are processed and there is no return value.

example:

/**
*	接受CF、BiConsumer,无返回值
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously");
    return "success";
});
// 异步运行交由默认线程池(forkjoinpool)无入参有返回值supply
CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success";
}, threadPoolExecutor);

CompletableFuture<Void> bothAsync = supplyCustomize.thenAcceptBothAsync(supply, (a, b) -> {
    
    
    System.out.println("thenAcceptBoth result " + a + "-" + b);
}, threadPoolExecutor);

result:

completablefuture supplys asynchronously
completablefuture supplys asynchronously in customize threadPool
thenAcceptBoth result success-success
null

3.runAfterBoth

There is no input parameter and no return value. The runAfterBoth method will only be executed after the previous two are completed. We can run the test in any CF simulation for a long time.

example:

CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
     System.out.println("completablefuture supplys asynchronously");
     return "success";
 });
 // 异步运行交由默认线程池(forkjoinpool)无入参有返回值supply
 CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
     System.out.println("completablefuture supplys asynchronously in customize threadPool");
     return "success";
 }, threadPoolExecutor);
 CompletableFuture<Void> bothAsync = supply.runAfterBothAsync(supplyCustomize, () -> {
    
    
     System.out.println("run After Both Async");
 });

result:

completablefuture supplys asynchronously
completablefuture supplys asynchronously in customize threadPool
run After Both Async

CompletableFuture combined API-any task is completed

1.acceptEither

Corresponding to thenAcceptBoth, if any of the two CompletableFutures is completed, the next asynchronous task will continue.

example:

/**
*	任务一耗时长,所以当任务二完成就会执行acceptEitherAsync
*	参数 Consumer 入参为执行完成任务返回值
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("completablefuture supplys asynchronously");
    return "success1";
CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success2";
}, threadPoolExecutor);
CompletableFuture<Void> future = supply.acceptEitherAsync(supplyCustomize, a -> {
    
    
    System.out.println("acceptEither result " + a);
});

result:

completablefuture supplys asynchronously in customize threadPool
acceptEither result success2
completablefuture supplys asynchronously

2.applyToEither

Corresponding to thenCombine, if any of the two CompletableFutures is completed, the next asynchronous task will continue.

example:

/**
*	参数为Function,有入参也有返回值
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("completablefuture supplys asynchronously");
    return "success1";
});
// 异步运行交由默认线程池(forkjoinpool)无入参有返回值supply
CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success2";
}, threadPoolExecutor);
CompletableFuture<String> future = supply.applyToEither(supplyCustomize, a -> {
    
    
    System.out.println("acceptEither result " + a);
    return "appleTo " + a;
});

3.runAfterEither

Corresponding to runAfterBoth, if any of the two CompletableFutures is completed, the next asynchronous task will continue.

/**
*	runnable 无入参无返回值
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("completablefuture supplys asynchronously");
    return "success1";
});
// 异步运行交由默认线程池(forkjoinpool)无入参有返回值supply
CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success2";
}, threadPoolExecutor);
CompletableFuture<Void> future = supply.runAfterEitherAsync(supplyCustomize, () -> {
    
    
    System.out.println("runAfterEither result ");
});

CompletableFuture之allOf|anyOf

1.allOf

The return value can only be obtained by completing all get methods. The return value is null. Each task return value needs to call each CompletableFuture.

example:

/**
*	当所有任务完成后,返回null,具体每个任务返回值,需要调用具体
*	异步任务获取
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("completablefuture supplys asynchronously");
    return "success1";
});
CompletableFuture<String> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println("completablefuture supplys asynchronously in customize threadPool");
    return "success2";
}, threadPoolExecutor);
CompletableFuture<Void> allOf = CompletableFuture.allOf(supply, supplyCustomize);
allOf.get();
System.out.println(supplyCustomize.get() + "=>" + supply.get());

result:

completablefuture supplys asynchronously in customize threadPool
completablefuture supplys asynchronously
success2==success1

2.anyOf

/**
* 任一先执行完毕的结果将会先返回
*/
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
    
    
try {
    
    
    TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
    
    
    e.printStackTrace();
}
	System.out.println("completablefuture supplys asynchronously");
	return "success1";
});
// 异步运行交由默认线程池(forkjoinpool)无入参有返回值supply
CompletableFuture<Integer> supplyCustomize = CompletableFuture.supplyAsync(() -> {
    
    
	System.out.println("completablefuture supplys asynchronously in customize threadPool");
	return 2;
}, threadPoolExecutor);
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(supply, supplyCustomize);
System.out.println(anyOf.get());

result: it is executed first, so 2 is returned first

completablefuture supplys asynchronously in customize threadPool
2
completablefuture supplys asynchronously

Summarize

CompletableFuture's rich asynchronous calling methods can help us avoid using the main thread to connect multiple asynchronous tasks, improve program performance, and speed up response. At the same time, the excellent exception handling mechanism can also perfectly solve the problem of asynchronous call errors.

Guess you like

Origin blog.csdn.net/qq_40922616/article/details/121045841