【JUC】CompletableFuture详解

Future接口(Java 5新增)

Future接口定义了操作异步任务 的一些方法(如获取异步任务的执行结果、取消任务、判断任务是否被取消/完成等),提供了一种异步并行计算的功能,其实就是定义了一些规范,来保证异步任务执行的状态反馈 +获取任务执行的结果

举个🌰:主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就可以去做其他事情,过了一会获取子任务的执行结果或者变更的任务状态(是否有返回值 ,是否抛出异常等)

总结下出现的原因+作用:

Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务

FutureTask(Future接口的常见实现类)

还记得上文中举的例子吗?主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就可以去做其他事情,过了一会获取子任务的执行结果或者变更的任务状态(是否有返回值 ,是否抛出异常等)。

我们拿生活中的一个非常常见的场景来打个比方:小明要做咖喱牛肉饭,他需要蒸一份米饭+炒一份咖喱牛肉,于是小明把生米准备好放进电饭锅,选择煮饭模式,接着他就去洗菜切菜炒菜了,在菜炒快炒好的时候,电饭锅"滴"了一声,提示他提示米饭🍚已蒸好。

在这个场景下,我们就需要的实现类至少要满足3个条件:

  • 多线程(另外的电饭锅煮饭而不是都用一个炒菜锅)
  • 能获取返回值(饭是否蒸好了)
  • 异步(炒菜的同时也在蒸饭)

接下来我们就来看看我们的FutureTask

可以看到FutureTask实现了RunnableFuture接口,并且构造方法中可以接收实现了Callable接口的变量(有返回值),因此FutureTask是符合这三个条件的

本源的Future接口相关架构

FutureTask<V>类实现了RunnableFuture<V>接口

RunnableFuture<V> 接口继承了Runnable接口和 Future<V>接口

因此它们的关系如下:

FutureTask<V>类有两个构造方法,一个需要传Callcable<V>类型的参数,一个需要传Runnable类型的参数

优点 vs. 缺点

首先,一个很直接的优点就是:FutureTask+线程池异步多线程任务配合,能显著提高程序的执行效率。其次,它也有一些缺点:

调用get()可能会阻塞

调用get()就会马上去拿结果,如果立马拿不到结果就会阻塞(未计算完成会阻塞主线程),可以通过加超时设置来使用(但不优雅= =)

isDone()轮询可能导致CPU空转

轮询的方式会耗费CPU资源(并且不优雅= =/),而且也不一定能及时的得到计算结果(但优于可能会导致阻塞的方式)

因此我们可以得到结论:FutureTask对结果的获取不是十分的友好(要么轮训要么阻塞),因此对于简单的业务场景使用FutureTask一般是没有什么问题的,但是如果场景复杂起来,比如需要一堆异步计算的结果值进行叠加,后一个异步计算需要依赖前一个异步计算的结果值等等,使用FutureTask的那些API就不那么方便了,于是我们有了CompletableFuture

CompletableFuture对Future的改进

FutureTaskget()方法会导致阻塞(这与异步编程的设计理念相违背),而isDone()方法耗费CPU,其实比较理想的方式是通过传入回调函数,在FutureTask执行完成时自动调用该回调函数,这样就不用等待结果。

于是,JDK8出现了ComplateblFutureComplateblFuture实现了CompletionStageFuture,提供了非常强大的Future的扩展功能,可以极大的简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合的方法,它可能代表一个明确完成的Future,也可能代表一个完成阶段的CompletionStage,它支持在计算完以后触发一些函数或者执行某些动作。一句话概括下:FutureTask能干的CompletableFuture都能干(完全能取代Future接口),而且CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方,更加优雅。

从源码中可以看到CompletableFuture实现了CompletionStageFuture接口

CompletionStage接口

CompletionStage代表异步计算过程中的某一个阶段(一个阶段完成后可能会触发另一个阶段)

如何创建一个异步任务

官方并不建议直接new一个CompletableFuture,因为这样创建出来的CompletableFuture是不完整的

那么,我们该如何创建一个CompletableFuture呢?

4大核心静态方法

建议使用如下4个静态方法进行获取

其中runAsync()是无返回值的,而supplyAsync()是有返回值的

对于无返回值的runAsync(),如果传入了线程池对象,那么就会使用传入的线程池,否则就使用默认的ForkJoinPool线程池

同样的,对于有返回值的supplyAsync(),如果传入了线程池对象,那么就会使用传入的线程池,否则就使用默认的ForkJoinPool线程池

CompletableFuture的优点

异步任务结束时,会自动回调指定的方法,主线程社设置好回调以后,不用再去关注异步任务的执行,异步任务之间可以顺序执行,异步任务出错❌时,会自动调用指定的方法,减少了阻塞和轮询,是Future的增强版!

CompletableFuture常用方法

获得结果和触发计算

join():作用和get()一致,不过不抛出异常

getNow():折中处理,没有立刻计算完成,提供"备胎值";

complete():不抛异常,不阻塞,true时返回计算值;false时返回括号内的值

对计算结果进行处理

thanApply():计算结果存在依赖关系(串行化),前一步有异常就会"叫停",不会走后一步

默认线程池(ForkJoinPool)会在主线程结束后立刻关闭,建议自定义线程池

handle:计算结果存在依赖关系(串行化),前一步有异常依旧会走后一步

对计算结果进行消费

thanAccept():接收任务的处理结果并消费处理,无返回值

对比补充(任务之间的顺序执行)
less 复制代码
System.out.println(CompletableFuture.supplyAsync(() -> "hehe~").thenRun(() -> {}).join());
System.out.println(CompletableFuture.supplyAsync(() -> "hehehe~").thenAccept(r -> System.out.println(r)).join());
System.out.println(CompletableFuture.supplyAsync(() -> "hehehehe~").thenApply(r -> r + "dadada~").join());

对计算结果进行选用

applyToEither():谁快用谁

对计算结果进行合并

thenCombine()

相关推荐
绝顶少年1 分钟前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端
心灵宝贝1 分钟前
Tomcat 部署 Jenkins.war 详细教程(含常见问题解决)
java·tomcat·jenkins
天上掉下来个程小白3 分钟前
Redis-14.在Java中操作Redis-Spring Data Redis使用方式-操作列表类型的数据
java·redis·spring·springboot·苍穹外卖
ゞ 正在缓冲99%…11 分钟前
leetcode22.括号生成
java·算法·leetcode·回溯
写代码的小王吧15 分钟前
【Java可执行命令】(十)JAR文件签名工具 jarsigner:通过数字签名及验证保证代码信任与安全,深入解析 Java的 jarsigner命令~
java·开发语言·网络·安全·web安全·网络安全·jar
孪生质数-15 分钟前
SQL server 2022和SSMS的使用案例1
网络·数据库·后端·科技·架构
uhakadotcom19 分钟前
AWS Lightsail 简介与实践
后端·面试·github
伊成28 分钟前
Springboot整合Mybatis+Maven+Thymeleaf学生成绩管理系统
java·maven·mybatis·springboot·学生成绩管理系统
一人の梅雨42 分钟前
化工网平台API接口开发实战:从接入到数据解析‌
java·开发语言·数据库
扫地的小何尚1 小时前
NVIDIA工业设施数字孪生中的机器人模拟
android·java·c++·链表·语言模型·机器人·gpu