CompletableFuture用法+四种任务原型+CompletionStage接口!

B站影视 电影资讯 2025-05-18 17:02 3

摘要:从JDK 8开始,在Concurrent包中提供了一个强大的异步编程工具CompletableFuture。在JDK8之前,异步编程可以通过线程池和Future来实现,但功能还不够强大。CompletableFuture的出现,使Java的异步编程能力向前迈进

从JDK 8开始,在Concurrent包中提供了一个强大的异步编程工具CompletableFuture。在JDK8之前,异步编程可以通过线程池和Future来实现,但功能还不够强大。CompletableFuture的出现,使Java的异步编程能力向前迈进了一大步。

在探讨CompletableFuture的原理之前,先详细看一下CompletableFuture的用法,从这些用法中,可以看到相较之前的Future有哪些能力得到了提升。

最简单的用法

CompletableFuture实现了Future接口,所以它也具有Future的特性:调用get方法会阻塞在那,直到结果返回。

另外1个线程调用complete方法完成该Future,则所有阻塞在get方法的线程都将获得返回结果。

提交任务:runAsync与supplyAsync

上面的例子是一个空的任务,下面尝试提交一个真的任务,然后等待结果返回。

例1:runAsync(Runnable)

CompletableFuture.runAsync ( .. ) 传 入 的 是 一 个 Runnable 接口,在上面的代码中是使用了Java 8的lambda表达式的写法,和定义一个Runnable对象是等价的。

例2:supplyAsync(Supplier)

例2和例1的区别在于,例2的任务有返回值。没有返回值的任务,提交的是Runnable,返回的是 CompletableFuture;;

有返回值的任务,提交的是 Supplier,返回的是CompletableFuture;。Supplier和前面的Callable很相似。

通过上面两个例子可以看出,在基本的用法上,CompletableFuture和Future很相似,都可以提交两类任务:一类是无返回值的,另一类是有返回值的。

链式的CompletableFuture:thenRun、thenAccept和thenApply

对于 Future,在提交任务之后,只能调用 get等结果返回;

但对于 CompletableFuture,可以在结果上面再加一个callback,当得到结果之后,再接着执行callback。

例1:thenRun(Runnable)

例2:thenAccept(Consumer)

例3:thenApply(Function)

三个例子都是在任务执行完成之后,再紧急执行一个callback,只是callback的形式有所区别:

(1)thenRun后面跟的是一个无参数、无返回值的方法,即Runnable,所以最终的返回值是CompletableFuture;类型。

(2)thenAccept 后面跟的是一个有参数、无返回值的方法,称为 Consumer,返回值也是CompletableFuture;类型。

顾名思义,只进不出,所以称为Consumer;前面的Supplier,是无参数,有返回值,只出不进,和Consumer刚好相反。

(3) thenApply 后面跟的是一个有参数、有返回值的方法,称为 Function。返回值是CompletableFuture;类型。

而参数接收的是前一个任务,即 supplyAsync(..)这个任务的返回值。因此这里只能用supplyAsync,不能用runAsync。因为runAsync没有返回值,不能为下一个链式方法传入参数。

CompletableFuture的组合:thenCompose与thenCombine

例1:thenCompose

在上面的例子中,thenApply接收的是一个Function,但是这个Function的返回值是一个通常的基本数据类型或一个对象,而不是另外一个 CompletableFuture。如果 Function 的返回值也是一个CompletableFuture,就会出现嵌套的CompletableFuture。考虑下面的例子:

函数1:输入userId,返回user对象。但由于是异步调用,所以返回了一个CompletableFuture;对象。

函数2:输入user对象,获取该user的信用卡积分(Double类型)。由于是异步调用,返回的不是Double,而是CompletableFuture;。

这两个函数链式调用,代码如下所示。

因为函数 getCreditRating(user)返回的是一个 CompletableFuture;类型,所以在链式调用之后,返回值变成一个嵌套的数据类型CompletableFuture;>;。

如果希望返回值是一个展平的CompletableFuture,可以使用thenCompose,代码如下所示。

下面是thenCompose函数的接口定义。

从该函数的定义可以看出,它传入的参数是一个Function类型,并且Function的返回值必须是CompletionStage的子类,也就是CompletableFuture类型。

例2:thenCombine

thenCombine函数的接口定义如下,从传入的参数可以看出,它不同于thenCompose。

第1个参数是一个CompletableFuture类型,第2个参数是一个函数,并且是一个BiFunction,也就是该函数有2个输入参数,1个返回值。

从该接口的定义可以大致推测,它是要在2个 CompletableFuture完成之后,把2个CompletableFuture的返回值传进去,再额外做一些事情。实例如下:

函数1:获取体重。

函数2:获取身高。

函数3:结合身高、体重,计算BMI指数。

任意个CompletableFuture的组合

上面的thenCompose和thenCombine只能组合2个CompletableFuture,而接下来的allOf 和anyOf 可以组合任意多个CompletableFuture。函数接口定义如下所示。

首先,这两个函数都是静态函数,参数是变长的CompletableFuture的集合。其次,allOf和anyOf的区别,前者是“与”,后者是“或”。

例1:allOf

allOf的返回值是CompletableFuture;类型,这是因为每个传入的CompletableFuture的返回值都可能不同,所以组合的结果是无法用某种类型来表示的,索性返回Void类型。那么,如何获取每个CompletableFuture的执行结果呢?参看下面的例子:

并行地下载100个网页。待下载完成之后,统计在100个网页中,含有某个单词的网页个数。整个过程可以分为三步:

第1步:并行下载100个网页。

第2步:通过allOf,等待所有网页下载完毕,收集返回结果。

关键点:因为allOf没有返回值,所以通过thenApply,给allFutures附上一个回调函数。在回调函数里面,依次调用每个future的get函数,获取到100个结果,存入List;。

第3步:统计在这100个网页中,含有单词“XXX”的网页的个数。

例2:anyOf

anyOf 的含义是只要有任意一个 CompletableFuture 结束,就可以做接下来的事情,而无须像AllOf那样,等待所有的CompletableFuture结束。

但由于每个CompletableFuture的返回值类型都可能不同,任意一个,意味着无法判断是什么类型,所以anyOf的返回值是CompletableFuture;类型。

考虑下面的例子。

在该例子中,因为future1、future2、future3的返回值都是CompletableFuture;类型,所以anyOf返回的Object,一定也是String类型。

并且在3个future中,future2睡眠时间最短,会最先执行完成,anyOfFuture.get获取的也就是future2的内容。future1、future3的返回结果被丢弃了。

四种任务原型

通过上面的例子可以总结出,提交给CompletableFuture执行的任务有四种类型:Runnable、Consumer、Supplier、Function。表格8-1总结了这四种任务原型的对比。

runAsync 与 supplierAsync 是 CompletableFutre 的 静 态 方法;而 thenAccept、thenAsync、thenApply是CompletableFutre的成员方法。

因为初始的时候没有CompletableFuture对象,也没有参数可传,所以提交的只能是Runnable或者Supplier,只能是静态方法;

通过静态方法生成CompletableFuture对象之后,便可以链式地提交其他任务了,这个时候就可以提交Runnable、Consumer、Function,且都是成员方法。

CompletableFuture不仅实现了Future接口,还实现了CompletableStage接口,如下所示。

CompletionStage接口定义的正是前面的各种链式方法、组合方法,如下所示。

关于CompletionStage接口,有几个关键点要说明:

(1)所有方法的返回值都是CompletionStage类型,也就是它自己。正因为如此,才能实现如下的链式调用:future1.thenApply(xxx).thenApply(xxx).thenCompose(…).thenRun(..)。

(2)thenApply接受的是一个有输入参数、返回值的Function。

这个Function的输入参数,必须是?Super T 类型,也就是T或者T的父类型,而T必须是调用thenApplycompletableFuture对象的类型;返回值则必须是?Extends U类型,也就是U或者U的子类型,而U恰好是thenApply的返回值的CompletionStage对应的类型。其他函数,诸如thenCompose、thenCombine也是类似的原理。

来源:程序员高级码农II一点号

相关推荐