CompletableFuture makes asynchronous programming fly

CompletableFuture understanding

CompletableFutureIt is the main tool newly added in the java.util.concurrentlibrary . Compared with the traditional one, it supports many new features such as streaming computing, functional programming, completion notification, custom exception handling, etc.java 8Future

CompletableFutureImplemented CompletionStageinterface and Futureinterface, the former is an extension of the latter, adding the ability of asynchronous callback , stream processing, and multiple Futurecombined processing, making Javait smoother and more convenient when dealing with multi-task collaborative work.
Click here to learn about the JMM thread pool explain

CompletableFuture And  the implementation class that FutureTask belongs to the same  Future interface can get the execution result of the thread
insert image description here

1.2 Create CompletableFuture

CompletableFuture When creating, if the thread pool is passed in, it will go to the specified thread pool to work. If it is not passed in,  ForkJoinPool
ForkJoinPoolthe advantage of going back to the default is that you can make full use of the advantages of multiple cpuand multi-core cpu, split a task into multiple 小任务, and put the multiple 小任务on multiple processor cores for parallel execution; when multiple 小任务executions are completed After that, these execution results can be combined.

ForkJoinPoolYes ExecutorServicethe implementation class, thus a special kind of thread pool.
How to use: ForkJoinPoolAfter creating an instance, you can call ForkJoinPoolthe submit(ForkJoinTask<T> task) or invoke(ForkJoinTask<T> task)method to perform the specified task.
where ForkJoinTaskrepresents a task that can be parallelized and merged. ForkJoinTaskis an abstract class with two abstract subclasses: RecusiveAction和RecusiveTask. which RecusiveTaskrepresent tasks with a return value, and RecusiveActionrepresent tasks without a return value

1.2.1  Constructor Creation

CompletableFutureThe easiest way is to create an instance through the constructor . As shown in the code below. CompletableFutureSince there is no calculation result for the newly created one , at this time join, the current thread will always be blocked here.

CompletableFuture<String> future = new CompletableFuture();
String result = future.join();
System.out.println(result);

123
复制代码

At this point, if the value is set actively in another thread CompletableFuture, the result in the above thread can be returned.

future.complete("test");

1
复制代码

1.2.2 supplyAsync creation

CompletableFuture.supplyAsync()Can also be used to create CompletableFutureinstances. The instance created by this function CompletableFuturewill asynchronously execute the currently passed computing task. On the calling side, the get或joinfinal calculation result can be obtained by .

supplyAsyncThere are two kinds of signatures:

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

12
复制代码

The first one only needs to pass in an Supplierinstance (usually using lamdaan expression), and the framework will use the thread pool by default ForkJointo execute the submitted tasks.
The second can specify a custom thread pool, and then submit tasks to the thread pool for execution.
Below is an example supplyAsynccreated using CompletableFuture:

CompletableFuture<String> future 
	= CompletableFuture.supplyAsync(()->{
      System.out.println("compute test");
      return "test";
});
 
String result = future.join();
System.out.println("get result: " + result);

12345678
复制代码

In the example, the asynchronous task will print out compute testand return it testas the final calculation result. So, the final print information isget result: test

1.2.3 runAsync creation

CompletableFuture.runAsync()Can also be used to create CompletableFutureinstances. The supplyAsync()difference is that the runAsync()incoming task requires a Runnabletype, so there is no return value. Therefore, it is runAsyncsuitable for creating computational tasks that do not require a return value. supplyAsync()Similarly, runAsync()there are two types of signatures :

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

123
复制代码

The following is runAsync()an example of use:

CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
            System.out.println("compute test");
        });

System.out.println("get result: " + future.join());

由于任务没有返回值, 所以最后的打印结果是"get result: null"1234567
复制代码

1.3 Asynchronous callback method

Future相比,CompletableFuture最大的不同是支持流式(Stream)的计算处理,多个任务之间,可以前后相连,从而形成一个计算流。比如:任务1产生的结果,可以直接作为任务2的入参,参与任务2的计算,以此类推。

CompletableFuture中常用的流式连接函数包括:

  • thenApply——有入参有返回
    thenApplyAsync——有入参有返回
  • thenAccept——有入参无返回
    thenAcceptAsync——有入参无返回
  • thenRun——无入参无返回
    thenRunAsync——无入参无返回
  • thenCombine
    thenCombineAsync
  • thenCompose
    thenComposeAsync
  • whenComplete
    whenCompleteAsync
  • handle
    handleAsync

其中,带Async后缀的函数表示需要连接的后置任务会被单独提交到线程池中,从而相对前置任务来说是异步运行的。除此之外,两者没有其他区别。因此,为了快速理解,在接下来的介绍中,我们主要介绍不带Async的版本。

1.3.1 thenApply / thenAccept / thenRun互相依赖

这里将thenApply / thenAccept / thenRun放在一起讲,因为这几个连接函数之间的唯一区别是提交的任务类型不一样 :

  • thenApply提交的任务类型需遵从Function签名,也就是有入参和返回值,其中入参为前置任务的结果
  • thenAccept提交的任务类型需遵从Consumer签名,也就是有入参但是没有返回值,其中入参为前置任务的结果
  • thenRun提交的任务类型需遵从Runnable签名,即没有入参也没有返回值
CompletableFuture<Integer> future1 
	= CompletableFuture.supplyAsync(()->{
     	System.out.println("compute 1");
     	return 1;
 });
 CompletableFuture<Integer> future2 
 	= future1.thenApply((p)->{
	     System.out.println("compute 2");
	     return p+10;
 });
 System.out.println("result: " + future2.join());

1234567891011
复制代码

在上面的示例中,future1通过调用thenApply将后置任务连接起来,并形成future2。该示例的最终打印结果为11,可见程序在运行中,future1的结果计算出来后,会传递给通过thenApply连接的任务,从而产生future2的最终结果为1+10=11。当然,在实际使用中,我们理论上可以无限连接后续计算任务,从而实现链条更长的流式计算。

需要注意的是,通过thenApply / thenAccept / thenRun连接的任务,当且仅当前置任务计算完成时,才会开始后置任务的计算。因此,这组函数主要用于连接前后有依赖的任务链。

1.3.1.1 thenApply

thenApply 表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,测试用例如下:

@Test
public void test5() throws Exception {
        ForkJoinPool pool=new ForkJoinPool();
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job1,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job1,time->"+System.currentTimeMillis());
            return 1.2;
        },pool);
        //cf关联的异步任务的返回值作为方法入参,传入到thenApply的方法中
        //thenApply这里实际创建了一个新的CompletableFuture实例
        CompletableFuture<String> cf2=cf.thenApply((result)->{
            System.out.println(Thread.currentThread()+" start job2,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job2,time->"+System.currentTimeMillis());
            return "test:"+result;
        });
        System.out.println("main thread start cf.get(),time->"+System.currentTimeMillis());
        //等待子任务执行完成
        System.out.println("run result->"+cf.get());
        System.out.println("main thread start cf2.get(),time->"+System.currentTimeMillis());
        System.out.println("run result->"+cf2.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

12345678910111213141516171819202122232425262728293031
复制代码

在这里插入图片描述
job1执行结束后,将job1的方法返回值作为入参传递到job2中并立即执行job2。thenApplyAsync与thenApply的区别在于,前者是将job2提交到线程池中异步执行,实际执行job2的线程可能是另外一个线程,后者是由执行job1的线程立即执行job2,即两个job都是同一个线程执行的。将上述测试用例中thenApply改成thenApplyAsync后,执行结果如下:
在这里插入图片描述
从输出可知,执行job1和job2是两个不同的线程。thenApplyAsync有一个重载版本,可以指定执行异步任务的Executor实现,如果不指定,默认使用ForkJoinPool.commonPool()。 下述的多个方法,每个方法都有两个以Async结尾的方法,一个使用默认的Executor实现,一个使用指定的Executor实现,不带Async的方法是由触发该任务的线程执行该任务,带Async的方法是由触发该任务的线程将任务提交到线程池,执行任务的线程跟触发任务的线程不一定是同一个

1.3.1.2 thenAccept / thenRun

thenAccept同 thenApply 接收上一个任务的返回值作为参数,但是无返回值;thenRun 的方法没有入参,也买有返回值,测试用例如下:

@Test
    public void test6() throws Exception {
        ForkJoinPool pool=new ForkJoinPool();
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job1,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job1,time->"+System.currentTimeMillis());
            return 1.2;
        },pool);
        //cf关联的异步任务的返回值作为方法入参,传入到thenApply的方法中
        CompletableFuture cf2=cf.thenApply((result)->{
            System.out.println(Thread.currentThread()+" start job2,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job2,time->"+System.currentTimeMillis());
            return "test:"+result;
        }).thenAccept((result)-> { //接收上一个任务的执行结果作为入参,但是没有返回值
            System.out.println(Thread.currentThread()+" start job3,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(result);
            System.out.println(Thread.currentThread()+" exit job3,time->"+System.currentTimeMillis());
        }).thenRun(()->{ //无入参,也没有返回值
            System.out.println(Thread.currentThread()+" start job4,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println("thenRun do something");
            System.out.println(Thread.currentThread()+" exit job4,time->"+System.currentTimeMillis());
        });
        System.out.println("main thread start cf.get(),time->"+System.currentTimeMillis());
        //等待子任务执行完成
        System.out.println("run result->"+cf.get());
        System.out.println("main thread start cf2.get(),time->"+System.currentTimeMillis());
        //cf2 等待最后一个thenRun执行完成
        System.out.println("run result->"+cf2.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
复制代码

执行结果
在这里插入图片描述

1.3.2 exceptionally有返回

exceptionally方法指定某个任务执行异常时执行的回调方法,会将抛出异常作为参数传递到回调方法中,如果该任务正常执行则会exceptionally方法返回的CompletionStageresult就是该任务正常执行的结果

@Test
    public void test2() throws Exception {
        ForkJoinPool pool=new ForkJoinPool();
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+"job1 start,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            if(true){
                throw new RuntimeException("test");
            }else{
                System.out.println(Thread.currentThread()+"job1 exit,time->"+System.currentTimeMillis());
                return 1.2;
            }
        },pool);
        //cf执行异常时,将抛出的异常作为入参传递给回调方法
        CompletableFuture<Double> cf2= cf.exceptionally((param)->{
             System.out.println(Thread.currentThread()+" start,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println("error stack trace->");
            param.printStackTrace();
            System.out.println(Thread.currentThread()+" exit,time->"+System.currentTimeMillis());
             return -1.1;
        });
        //cf正常执行时执行的逻辑,如果执行异常则不调用此逻辑
        CompletableFuture cf3=cf.thenAccept((param)->{
            System.out.println(Thread.currentThread()+"job2 start,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println("param->"+param);
            System.out.println(Thread.currentThread()+"job2 exit,time->"+System.currentTimeMillis());
        });
        System.out.println("main thread start,time->"+System.currentTimeMillis());
        //等待子任务执行完成,此处无论是job2和job3都可以实现job2退出,主线程才退出,如果是cf,则主线程不会等待job2执行完成自动退出了
        //cf2.get时,没有异常,但是依然有返回值,就是cf的返回值
        System.out.println("run result->"+cf2.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

123456789101112131415161718192021222324252627282930313233343536373839404142434445
复制代码

在这里插入图片描述

1.3.3 whenComplete无返回

whenComplete主要用于注入任务完成时的回调通知逻辑。这个解决了传统future在任务完成时,无法主动发起通知的问题。前置任务会将计算结果或者抛出的异常作为入参传递给回调通知函数。

以下为示例:

CompletableFuture<Integer> future1 
	= CompletableFuture.supplyAsync(()->{
	    System.out.println("compute 1");
	    return 1;
});
CompletableFuture future2 
	= future1.whenComplete((r, e)->{
	    if(e != null){
	        System.out.println("compute failed!");
	    } else {
	        System.out.println("received result is " + r);
	    }
});
System.out.println("result: " + future2.join());

1234567891011121314
复制代码

需要注意的是,future2获得的结果是前置任务的结果,whenComplete中的逻辑不会影响计算结果。

@Test
    public void test10() throws Exception {
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+"job1 start,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            if(false){
                throw new RuntimeException("test");
            }else{
                System.out.println(Thread.currentThread()+"job1 exit,time->"+System.currentTimeMillis());
                return 1.2;
            }
        });
        //cf执行完成后会将执行结果和执行过程中抛出的异常传入回调方法,如果是正常执行的则传入的异常为null
        CompletableFuture<Double> cf2=cf.whenComplete((a,b)->{
            System.out.println(Thread.currentThread()+"job2 start,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            if(b!=null){
                System.out.println("error stack trace->");
                b.printStackTrace();
            }else{
                System.out.println("run succ,result->"+a);
            }
            System.out.println(Thread.currentThread()+"job2 exit,time->"+System.currentTimeMillis());
        });
        //等待子任务执行完成
        System.out.println("main thread start wait,time->"+System.currentTimeMillis());
        //如果cf是正常执行的,cf2.get的结果就是cf执行的结果
        //如果cf是执行异常,则cf2.get会抛出异常
        System.out.println("run result->"+cf2.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

1234567891011121314151617181920212223242526272829303132333435363738
复制代码

1.3.4 handle有返回

handlewhenComplete的作用有些类似,但是handle接收的处理函数有返回值,而且返回值会影响最终获取的计算结果。handle方法返回的CompletableFutureresult是回调方法的执行结果或者回调方法执行期间抛出的异常,与原始CompletableFutureresult无关了

以下为示例:

CompletableFuture<Integer> future1
	 = CompletableFuture.supplyAsync(()->{
	     System.out.println("compute 1");
	     return 1;
 });
 CompletableFuture<Integer> future2
 	 = future1.handle((r, e)->{
	     if(e != null){
	         System.out.println("compute failed!");
	         return r;
	     } else {
	         System.out.println("received result is " + r);
	         return r + 10;
	     }
 });
 System.out.println("result: " + future2.join());

12345678910111213141516
复制代码

在以上示例中,打印出的最终结果为11。说明经过handle计算后产生了新的结果

 @Test
    public void test10() throws Exception {
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+"job1 start,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            if(true){
                throw new RuntimeException("test");
            }else{
                System.out.println(Thread.currentThread()+"job1 exit,time->"+System.currentTimeMillis());
                return 1.2;
            }
        });
        //cf执行完成后会将执行结果和执行过程中抛出的异常传入回调方法,如果是正常执行的则传入的异常为null
        CompletableFuture<String> cf2=cf.handle((a,b)->{
            System.out.println(Thread.currentThread()+"job2 start,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            if(b!=null){
                System.out.println("error stack trace->");
                b.printStackTrace();
            }else{
                System.out.println("run succ,result->"+a);
            }
            System.out.println(Thread.currentThread()+"job2 exit,time->"+System.currentTimeMillis());
            if(b!=null){
                return "run error";
            }else{
                return "run succ";
            }
        });
        //等待子任务执行完成
        System.out.println("main thread start wait,time->"+System.currentTimeMillis());
        //get的结果是cf2的返回值,跟cf没关系了
        System.out.println("run result->"+cf2.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

123456789101112131415161718192021222324252627282930313233343536373839404142
复制代码

1.4 异步组合方法

1.4.1 thenCombine / thenAcceptBoth / runAfterBoth互相不依赖

这三个方法都是将两个CompletableFuture组合起来,只有这两个都正常执行完了才会执行某个任务,区别在于,thenCombine会将两个任务的执行结果作为方法入参传递到指定方法中,且该方法有返回值;thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;runAfterBoth没有入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果

thenCombine最大的不同是连接任务可以是一个独立的CompletableFuture(或者是任意实现了CompletionStage的类型),从而允许前后连接的两个任务可以并行执行(后置任务不需要等待前置任务执行完成),最后当两个任务均完成时,再将其结果同时传递给下游处理任务,从而得到最终结果。

CompletableFuture<Integer> future1 
	= CompletableFuture.supplyAsync(()->{
     System.out.println("compute 1");
     return 1;
 });
 CompletableFuture<Integer> future2 
 	= CompletableFuture.supplyAsync(()->{
     System.out.println("compute 2");
     return 10;
 });
 CompletableFuture<Integer> future3 
 	= future1.thenCombine(future2, (r1, r2)->r1 + r2);
 System.out.println("result: " + future3.join());

12345678910111213
复制代码

上面示例代码中,future1future2为独立的CompletableFuture任务,他们分别会在各自的线程中并行执行,然后future1通过thenCombinefuture2连接,并且以lamda表达式传入处理结果的表达式,该表达式代表的任务会将future1与future2的结果作为入参并计算他们的和。
因此,上面示例代码中,最终的打印结果是11。

一般,在连接任务之间互相不依赖的情况下,可以使用thenCombine来连接任务,从而提升任务之间的并发度。

注意,thenAcceptBoth、thenAcceptBothAsync、runAfterBoth、runAfterBothAsync的作用与thenConbime类似,唯一不同的地方是任务类型不同,分别是BiConumser、Runnable

@Test
    public void test7() throws Exception {
        ForkJoinPool pool=new ForkJoinPool();
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job1,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job1,time->"+System.currentTimeMillis());
            return 1.2;
        });
        CompletableFuture<Double> cf2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job2,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job2,time->"+System.currentTimeMillis());
            return 3.2;
        });
        //cf和cf2的异步任务都执行完成后,会将其执行结果作为方法入参传递给cf3,且有返回值
        CompletableFuture<Double> cf3=cf.thenCombine(cf2,(a,b)->{
            System.out.println(Thread.currentThread()+" start job3,time->"+System.currentTimeMillis());
            System.out.println("job3 param a->"+a+",b->"+b);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job3,time->"+System.currentTimeMillis());
            return a+b;
        });
 
        //cf和cf2的异步任务都执行完成后,会将其执行结果作为方法入参传递给cf3,无返回值
        CompletableFuture cf4=cf.thenAcceptBoth(cf2,(a,b)->{
            System.out.println(Thread.currentThread()+" start job4,time->"+System.currentTimeMillis());
            System.out.println("job4 param a->"+a+",b->"+b);
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job4,time->"+System.currentTimeMillis());
        });
 
        //cf4和cf3都执行完成后,执行cf5,无入参,无返回值
        CompletableFuture cf5=cf4.runAfterBoth(cf3,()->{
            System.out.println(Thread.currentThread()+" start job5,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("cf5 do something");
            System.out.println(Thread.currentThread()+" exit job5,time->"+System.currentTimeMillis());
        });
 
        System.out.println("main thread start cf.get(),time->"+System.currentTimeMillis());
        //等待子任务执行完成
        System.out.println("cf run result->"+cf.get());
        System.out.println("main thread start cf5.get(),time->"+System.currentTimeMillis());
        System.out.println("cf5 run result->"+cf5.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
复制代码

1.4.2 applyToEither / acceptEither / runAfterEither

这三个方法都是将两个CompletableFuture组合起来,只要其中一个执行完了就会执行某个任务(其他线程依然会继续执行),其区别在于applyToEither会将已经执行完成的任务的执行结果作为方法入参,并有返回值;acceptEither同样将已经执行完成的任务的执行结果作为方法入参,但是没有返回值;runAfterEither没有方法入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。测试用例如下

@Test
    public void test8() throws Exception {
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job1,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job1,time->"+System.currentTimeMillis());
            return 1.2;
        });
        CompletableFuture<Double> cf2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job2,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job2,time->"+System.currentTimeMillis());
            return 3.2;
        });
        //cf和cf2的异步任务都执行完成后,会将其执行结果作为方法入参传递给cf3,且有返回值
        CompletableFuture<Double> cf3=cf.applyToEither(cf2,(result)->{
            System.out.println(Thread.currentThread()+" start job3,time->"+System.currentTimeMillis());
            System.out.println("job3 param result->"+result);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job3,time->"+System.currentTimeMillis());
            return result;
        });
 
        //cf和cf2的异步任务都执行完成后,会将其执行结果作为方法入参传递给cf3,无返回值
        CompletableFuture cf4=cf.acceptEither(cf2,(result)->{
            System.out.println(Thread.currentThread()+" start job4,time->"+System.currentTimeMillis());
            System.out.println("job4 param result->"+result);
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job4,time->"+System.currentTimeMillis());
        });
 
        //cf4和cf3都执行完成后,执行cf5,无入参,无返回值
        CompletableFuture cf5=cf4.runAfterEither(cf3,()->{
            System.out.println(Thread.currentThread()+" start job5,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("cf5 do something");
            System.out.println(Thread.currentThread()+" exit job5,time->"+System.currentTimeMillis());
        });
 
        System.out.println("main thread start cf.get(),time->"+System.currentTimeMillis());
        //等待子任务执行完成
        System.out.println("cf run result->"+cf.get());
        System.out.println("main thread start cf5.get(),time->"+System.currentTimeMillis());
        System.out.println("cf5 run result->"+cf5.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
复制代码

1.4.3 thenCompose互相依赖

前面讲了thenCombine主要用于没有前后依赖关系之间的任务进行连接。那么,如果两个任务之间有前后依赖关系,但是连接任务又是独立的CompletableFuture,该怎么实现呢?

先来看一下直接使用thenApply实现:

CompletableFuture<Integer> future1 
	= CompletableFuture.supplyAsync(()->{
	     System.out.println("compute 1");
	     return 1;
 });
 CompletableFuture<CompletableFuture<Integer>> future2 
 	=  future1.thenApply(
 		(r)->CompletableFuture.supplyAsync(()->r+10));
 System.out.println(future2.join().join());

123456789
复制代码

可以发现,上面示例代码中,future2的类型变成了CompletableFuture嵌套,而且在获取结果的时候,也需要嵌套调用join或者get。这样,当连接的任务越多时,代码会变得越来越复杂,嵌套获取层级也越来越深。因此,需要一种方式,能将这种嵌套模式展开,使其没有那么多层级。thenCompose的主要目的就是解决这个问题(这里也可以将thenCompose的作用类比于stream接口中的flatMap,因为他们都可以将类型嵌套展开)。

看一下通过thenCompose如何实现上面的代码:

CompletableFuture<Integer> future1 
  = CompletableFuture.supplyAsync(()->{
      System.out.println("compute 1");
      return 1;
  });
CompletableFuture<Integer> future2 
  	= future1.thenCompose(
  		(r)->CompletableFuture.supplyAsync(()->r+10));
System.out.println(future2.join());

123456789
复制代码

通过示例代码可以看出来,很明显,在使用了thenCompose后,future2不再存在CompletableFuture类型嵌套了,从而比较简洁的达到了我们的目的。

thenComposeAfter the execution of a task is completed, the method will take the execution result of the task as a method parameter and execute the specified method. The method will return a new CompletableFutureinstance. If the CompletableFutureinstance is resultnot null, it will return a new instance based on resultthe CompletableFutureinstance; if the CompletableFutureinstance is nullthen, then execute this new task

    @Test
    public void test9() throws Exception {
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job1,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job1,time->"+System.currentTimeMillis());
            return 1.2;
        });
        CompletableFuture<String> cf2= cf.thenCompose((param)->{
            System.out.println(Thread.currentThread()+" start job2,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job2,time->"+System.currentTimeMillis());
            return CompletableFuture.supplyAsync(()->{
                System.out.println(Thread.currentThread()+" start job3,time->"+System.currentTimeMillis());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread()+" exit job3,time->"+System.currentTimeMillis());
                return "job3 test";
            });
        });
        System.out.println("main thread start cf.get(),time->"+System.currentTimeMillis());
        //等待子任务执行完成
        System.out.println("cf run result->"+cf.get());
        System.out.println("main thread start cf2.get(),time->"+System.currentTimeMillis());
        System.out.println("cf2 run result->"+cf2.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

123456789101112131415161718192021222324252627282930313233343536
复制代码

1.4.4 allOf / anyOf

allOfWhat is returned CompletableFutureis that multiple tasks are executed before they are executed. As long as there is an exception in the execution of one task, an exception will be thrown when the returned CompletableFutureexecution method is executed. If they are all executed normally, the returned is multiple tasks as long as one of them is executed. It will be executed when completed, and it returns the execution result of the task that has been executed. If the task executes abnormally, an exception is thrown.getgetnull
anyOfCompletableFutureget

 @Test
    public void test11() throws Exception {
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job1,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job1,time->"+System.currentTimeMillis());
            return 1.2;
        });
        CompletableFuture<Double> cf2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job2,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread()+" exit job2,time->"+System.currentTimeMillis());
            return 3.2;
        });
        CompletableFuture<Double> cf3 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" start job3,time->"+System.currentTimeMillis());
            try {
                Thread.sleep(1300);
            } catch (InterruptedException e) {
            }
//            throw new RuntimeException("test");
            System.out.println(Thread.currentThread()+" exit job3,time->"+System.currentTimeMillis());
            return 2.2;
        });
        //allof等待所有任务执行完成才执行cf4,如果有一个任务异常终止,则cf4.get时会抛出异常,都是正常执行,cf4.get返回null
        //anyOf是只有一个任务执行完成,无论是正常执行或者执行异常,都会执行cf4,cf4.get的结果就是已执行完成的任务的执行结果
        CompletableFuture cf4=CompletableFuture.allOf(cf,cf2,cf3).whenComplete((a,b)->{
           if(b!=null){
               System.out.println("error stack trace->");
               b.printStackTrace();
           }else{
               System.out.println("run succ,result->"+a);
           }
        });
 
        System.out.println("main thread start cf4.get(),time->"+System.currentTimeMillis());
        //等待子任务执行完成
        System.out.println("cf4 run result->"+cf4.get());
        System.out.println("main thread exit,time->"+System.currentTimeMillis());
    }

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
复制代码

The difference between join() and get() is that join() returns the result of the calculation or throws an unchecked exception (CompletionException), while get()  returns a specific exception
get()  can specify a timeout period

Guess you like

Origin juejin.im/post/7120139020394315790