CompletableFuture理解与应用
创始人
2024-01-25 04:32:35
0

背景:有需要用到异步编程的,需要响应式回调,不是单线程不好,redis就很棒。根据实际情况选择,这篇文章 介绍了CompletableFutureFutureRxJavaObservable之间的区别可以看看。

常用api:

部分不带后缀Async的和带的区别在于是否开启新线程处理结果,根据具体实际情况选用。
只演示有返回值的,没返回值的不掩饰。

  1. supplyAsync创建一个带返回值的异步任务。
  2. thenApplyAsync某个任务执行完执行的回调。
  3. exceptionally 某个任务执行异常时执行的回调方法。
  4. handleAsync某个任务执行完成后执行的回调方法,可以处理异常。
  5. thenCombine两个任务都正常执行完才执行后面任务。
  6. applyToEither只要其中一个执行完了就会执行某个任务。
  7. allOf多个任务都执行完成后才会执行。
  8. anyOf谁先执行完就持有谁。

supplyAsync示例:

runAsync功能类似只是没返回值不推荐

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}return 1.2;});System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);//获取结果System.out.println("最终结果:"+String.valueOf(f1.get()));return "success";}

输出:
可以看到主线程和子线程在运行,主线程阻塞等待子线程获取结果,不需要结果不调用get()方法即可实现异步。

http-nio-8080-exec-1
耗时:0
supplyAsync--->ForkJoinPool.commonPool-worker-1
最终结果:1.2

thenApplyAsync示例

可以在任务后面链式使用,thenApply,thenAccept,thenRun 功能类似不推荐,要么没入参要么没返回值
入参是上一个任务的返回值,出参和上一个任务的返回值类型一样。
可以写多个thenApplyAsync来处理后续任务。

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}return 1.2;}).thenApplyAsync(result->{System.out.println("thenApplyAsync->"+Thread.currentThread().getName());return result*2;}).thenApplyAsync(result->{System.out.println("thenApplyAsync->"+Thread.currentThread().getName());return result*2;});System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);//获取结果System.out.println("最终结果:"+String.valueOf(f1.get()));return "success";}

输出:
supplyAsync返回值是1.2,经过thenApplyAsync后续处理返回翻倍,最终返回4.8。
thenApplyAsync第一个任务和supplyAsync共用一个线程,第二个新开一个线程这就是后缀加Async作用。

http-nio-8080-exec-2
supplyAsync--->ForkJoinPool.commonPool-worker-1
耗时:0
thenApplyAsync->ForkJoinPool.commonPool-worker-1
thenApplyAsync->ForkJoinPool.commonPool-worker-2
最终结果:4.8

exceptionally示例

只有前置任务发生异常才会走这里。
当前置第一个任务发生异常后走这里,其他后续任务不走。
返回和前置任务一样类型的数据。

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}int a = 1/0;return 1.2;}).thenApplyAsync(result->{System.out.println("thenApplyAsync--->"+Thread.currentThread().getName());int a = 1/0;return result*2;}).exceptionally(p->{System.out.println("exceptionally--->"+Thread.currentThread().getName());return -0.1;});System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);//获取结果System.out.println("最终结果:"+String.valueOf(f1.get()));return "success";}

输出:
supplyAsync发生异常,跳过thenApplyAsync不执行,直接走exceptionally

http-nio-8080-exec-1
supplyAsync--->ForkJoinPool.commonPool-worker-1
耗时:0
exceptionally--->ForkJoinPool.commonPool-worker-1
最终结果:-0.1

whenCompleteAsync不演示

某个任务执行完成后执行的回调方法,接收2个参数,第一个是上一个任务的返回结果,第二个是异常,没返回值不演示。

handleAsync示例

whenComplete基本一致,区别在于handleAsync的回调方法有返回值,且返回的结果可以自己定义与上一个任务中的泛型没关系。
thenApplyAsync在这里抛出异常,handleAsync这里处理异常。

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}return 1.2;}).thenApplyAsync(result->{System.out.println("thenApplyAsync--->"+Thread.currentThread().getName());int a = 1/0;return result*2;});CompletableFuture f2 = f1.handleAsync((a, b) -> {System.out.println("handleAsync--->" + Thread.currentThread().getName());System.out.println("a--->"+a);System.out.println("b--->"+b);return "error";});System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);//获取结果System.out.println("最终结果:"+String.valueOf(f2.get()));return "success";}

输出:
报错之后,handleAsync获取不到之前的返回值,只能获取到异常信息,handleAsync中的和返回值。

http-nio-8080-exec-1
supplyAsync--->ForkJoinPool.commonPool-worker-1
耗时:0
thenApplyAsync--->ForkJoinPool.commonPool-worker-1
handleAsync--->ForkJoinPool.commonPool-worker-1
a--->null
b--->java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
最终结果:error

thenCombine示例

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

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync1--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}return 1.2;});CompletableFuture f2 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync2--->"+Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {}int a = 2/0;return 1.8;}).handle((a,b)->{if(b!=null){return 2.3;}else{return 3.3;}});CompletableFuture f4 = f1.thenCombineAsync(f2, (a, b) -> {System.out.println("thenCombineAsync1--->"+Thread.currentThread().getName());System.out.println("a--->"+a);System.out.println("b--->"+b);return a+b;});System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);//获取结果System.out.println("最终结果:"+String.valueOf(f4.get()));return "success";}

输出:
f2出现异常用handle处理,最终thenCombineAsync拿到返回结果。

http-nio-8080-exec-2
supplyAsync1--->ForkJoinPool.commonPool-worker-1
supplyAsync2--->ForkJoinPool.commonPool-worker-2
耗时:0
thenCombineAsync1--->ForkJoinPool.commonPool-worker-1
a--->1.2
b--->2.3
最终结果:3.5

applyToEither 示例

acceptEitherrunAfterEither都是将两个CompletableFuture组合起来,看谁跑得快就拿谁,其区别在于applyToEither有入参,有返回值;acceptEither方法入参,没有返回值;runAfterEither没方法入参,也没返回值。两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync1--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}return 1.2;});CompletableFuture f2 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync2--->"+Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {}int a = 2/0;return 1.8;}).handle((a,b)->{if(b!=null){return 2.3;}else{return 3.3;}}).applyToEither(f1,a->a);System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);//获取结果System.out.println("最终结果:"+String.valueOf(f2.get()));return "success";}

输出:
f2执行速度快返回2.3

http-nio-8080-exec-1
supplyAsync1--->ForkJoinPool.commonPool-worker-1
supplyAsync2--->ForkJoinPool.commonPool-worker-2
耗时:0
最终结果:2.3

allOf示例

所有任务都结束才结束

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync1--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}return 1.2;});CompletableFuture f2 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync2--->"+Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {}return 1.8;});CompletableFuture.allOf(f1,f2);//获取结果System.out.println("最终结果:"+String.valueOf(f1.get()));System.out.println("最终结果:"+String.valueOf(f2.get()));System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);return "success";}

输出:
耗时2秒,等待所有任务完成才结束

http-nio-8080-exec-2
supplyAsync1--->ForkJoinPool.commonPool-worker-1
supplyAsync2--->ForkJoinPool.commonPool-worker-2
最终结果:1.2
最终结果:1.8
耗时:2

anyOf示例

返回最先执行完的任务

    @GetMapping("/supplyAsync")public String supplyAsync() throws ExecutionException, InterruptedException {System.out.println(Thread.currentThread().getName());long l = System.currentTimeMillis();CompletableFuture f1 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync1--->"+Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {}return 1.2;});CompletableFuture f2 = CompletableFuture.supplyAsync(()->{System.out.println("supplyAsync2--->"+Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {}return 1.8;});CompletableFuture f3 = CompletableFuture.anyOf(f1, f2);//获取结果System.out.println("最终结果:"+String.valueOf(f3.get()));System.out.println("耗时:"+(System.currentTimeMillis()-l)/1000);return "success";}
 

输出
耗时1秒优先返回耗时最短的任务

http-nio-8080-exec-1
supplyAsync1--->ForkJoinPool.commonPool-worker-1
supplyAsync2--->ForkJoinPool.commonPool-worker-2
最终结果:1.8
耗时:1

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...