【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()

相关推荐
0xDevNull8 分钟前
Linux 中 Nginx 代理 Redis 的详细教程
redis·后端
GetcharZp23 分钟前
告别 Nginx 手动配置!这款 Go 语言开发的云原生网关,才是容器化时代的真香神器!
后端
jinanwuhuaguo26 分钟前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
RuoyiOffice32 分钟前
SpringBoot+Vue3 企业考勤如何处理法定假期?节假日方案、调休补班与工作日判断链路拆解
spring boot·后端·vue·anti-design-vue·ruoyioffice·假期·人力
xmjd msup1 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
Vane11 小时前
从零开发一个AI插件,经历了什么?
人工智能·后端
952361 小时前
SpringBoot统一功能处理
java·spring boot·后端
Lyyaoo.1 小时前
优惠券秒杀业务分析
java·开发语言
消失的旧时光-19431 小时前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法
勿忘初心12212 小时前
Java 国密 SM4 加密工具类实战(Hutool + BouncyCastle)|企业级数据加密 + 兼容 JDK8
java·数据安全·数据加密·后端开发·企业级开发·国密 sm4