CompletableFuture

目录

  • CompletableFuture与Future有什么不同
        • 开发背景
          • 在使用多线程处理任务时,经常会需要等待某一阶段的任务执行完成之后,根据阶段结果再开启新的异
          • 步任务。
          • 这个逻辑可以使用Future来实现,通过Future的isDone方法或get方法来判断异步任务是否完成或获取
          • 结果。这种方法的问题在于,isDone轮询会消耗CPU资源,并且不能够及时的获取的任务完成的状态;
          • get方法会使调用的线程阻塞,无法处理后续逻辑。
          • CompletableFuture提供了回调机制和丰富的Stream API,可以很好的完成异步任务的回调和后续任务
          • 处理。并且CompletableFuture可以很好的描述各个异步任务之间的关系,让逻辑更清晰直观。内部的
          • 无锁并发栈也能够高性能的完成异步任务之间的并行调用。
        • 对比举例
          • 模拟场景:
          • 小白去餐厅吃饭,点了一份西红柿鸡蛋和一碗米饭。点完之后就玩王者等吃饭。
          • 此时服务员开始做米饭,厨师开始炒菜。做好之后由服务员打饭上菜,叫小白吃饭。
        • Future实现
          • 结果
        • CompletableFuture实现
          • 结果
        • 不同点
          • 1. CompletableFuture是在前两个任务完成后,自动调用后续任务的;而Future则阻塞了一个线程,
          • 用来等待前两个任务完成。
          • 2. CompletableFuture获取结果的join方法抛出的是RuntimeException,而get方法抛出的是受检异
          • 常InterruptedException, ExecutionException
          • 3. Future需要显示的指定线程池,而CompletableFuture可以默认使用ForkJoinPool。
          • 4. CompletableFuture可以使用类似Stream表达式的API表现出任务之间的逻辑关系,而Future不能
          • 在语法上直观的表示出这个关系。
        • 相同点
          • 对于逻辑上需要获取异步线程计算结果的任务,二者都可能在获取结果时被阻塞。
  • CompletableFuture的基本使用
          • CompletableFuture提供的api,大概可以描述一下几种场景:
          • 1. 创建一个异步任务
          • 2. 获取任务的结果
          • 3. 在一个异步任务完成后执行某个逻辑
          • 4. 在两个任务都完成后执行某个逻辑
          • 5. 在两个任务任意一个完成后执行某个逻辑
          • 6. 任意多个任务都完成后执行某个逻辑
          • 7. 任意多个任务,其中一个完成后执行某个逻辑
    • 创建一个异步任务
          • CompletableFuture提供了两个API用于开启一个异步任务。分别是使用Runnable接口对象作为参数的
          • runAsync方法与使用Supplier接口对象对作为参数的supplyAsync方法。两种API的区别就是Runnable
          • 接口与Supplier接口的区别,即runAsync方法没有返回值,而supplyAsync方法有返回值。
          • 两个方法使用默认的ForkJoinPool作为线程池执行异步任务。如果需要指定其他的线程池,可以使用以
          • 下重载API:
          • 使用举例:
          • 执行结果:
    • 获取任务结果
          • 与Future的区别
          • CompletableFuture比Future多提供了两种获取结果的方法:getNow与Join。
          • 1. getNow类似于Java8的Stream中的optional.orElse()方法,当结果不存在或没有完成时返回给定的
          • 结果。
          • 2. join与get类似,都会阻塞线程,二者区别在于抛出的异常不同。join可能抛出unchecked
          • exception,不需要强制try catch。get在方法签名中声明了受检异常,必须要在编码时进行处理。
          • 使用举例
          • 结果
    • 任务完成后的回调
          • CompletableFuture的一大特性就是丰富的基于回调的API。当某一个阶段的任务完成后,有时希望能够
          • 及时的获取结果,并操作下一步的逻辑。如果使用Future来实现,那么可能需要阻塞的通过get获取结
          • 果,或不断的通过isDone方法轮询,确认任务是否完成。但使用CompletableFuture,这一切就可以很
          • 自然并高效的完成。并且CompletableFuture的回调对象可以是多个,多个回调对象会被依次回调执
          • 行。
          • 针对与某“一个”任务完成的回调,有多个API可以调用。这些API的区别基本在于入参与返回值,但执行
          • 的时机都是当前阶段的任务完成后。具体如下:
        • thenApplyXXX
          • 作用:当前阶段(调用该方法的CompletionStage对象)非异常完成时,以 当前阶段的结果为入参 ,执
          • 行给定的函数,函数有返回值。
          • 举例
        • thenAcceptXXXX
          • 作用:与thenApply类似。当前阶段非异常完成时, 以当前结果为入参 ,执行给定的consumer函数, 函
          • 数没有返回值 。方法返回CompletionStage对象,对象的阶段结果为null。
            • }
        • thenRunXXX
          • 作用:当前阶段非异常完成时,执行制定动作。 没有入参和返回值 。
        • whenCompleteXXX
          • 作用:当前阶段完成时给定的BiConsumer函数被调用, 入参为抛出的异常(正常结束时为null)和结
          • 果(异常结束时为null) , 函数无返回值 ,方法返回的CompletionStage对象,含的结果或异常与当前
          • 阶段相同(不改变上一阶段的结果)。
          • 特点:可同时处理正常与异常的情况。
        • handleXXX
          • 作用:当前阶段完成时给定的BiFunction函数被调用, 入参为抛出的异常(正常结束时为null)和结果
          • (异常结束时为null) , 函数有返回值 。返回的CompletionStage对象为函数式接口的结果包装。
        • thenComposeXXX
          • 作用:当前阶段非异常完成时,执行给定的函数。函数使用当前阶段的结果作为入参, 函数 返回另一个
          • CompletionStage对象。thenCompose方法会将这个对象展开,避免结果的嵌套。执行效果类似于
          • Stream中的flatMap。
          • 举例:
            • /**
          • thenCompose结果输出
          • thenApply结果输出
    • 依赖于两个任务的回调
          • 有时后续的任务可能需要依赖于前两个任务的结果,针对这种情况,CompletableFuture也提供了对应
          • 的API。分为两类,分别是前两个任务都完成,前两个任务任意一个完成后,调用后续任务执行逻辑。具
          • 体如下:
            • /**
      • 前置两个任务都完成后
        • thenCombineXXX
          • 当调用该方法的前置任务与给点的前置任务都完成时,执行BiFunction函数。 函数的入参为前置两阶段
          • 的返回结果,函数无返回值。 阶段的结果CompletableFuture对象中result字段值为函数计算结果。
        • thenAcceptBothXXX
          • 作用:与thenAccep类似,但依赖于两个阶段。当前阶段与给定的阶段都非异常的执行完成时, 以两个
          • 阶段的结果为入参 ,执行给定的函数, 函数无返回值 。阶段的结果CompletableFuture对象中result字
          • 段值为null。
        • runAfterBothXXX
          • 作用:与thenRun类似,当前阶段与给点的阶段都异常的完成时,执行给点的函数。 函数无入参,无返
          • 回值 。阶段的结果CompletableFuture对象中result字段值为null。
      • 前置任意一个任务完成后
        • applyToEitherXXX
          • 作用:当前阶段或给点的阶段任意一个非异常的完成时,执行给定的函数。 以完成阶段的结果作为函数
          • 的入参,函数有返回值。 阶段的结果CompletableFuture对象中result字段值为函数计算结果。
        • acceptEitherXXX
          • 作用:同applyToEither。但函数为consumer函数式接口, 无返回值 。
        • runAfterEitherXXX
          • 作用:同applyToEither。但函数为Runnable接口, 无入参,无返回值。
      • 举例
          • 输出结果
            • /**
            • * 依赖于两个前置任务的API举例
            • */
    • 依赖于多个任务的回调
          • 除了依赖于一个、两个前置任务的API,更常用的是基于批量任务完成时机的判断。CompletableFuture
          • 也提供了对应的api。
      • 任意一个完成后--anyOf
          • CompletableFuture提供了静态方法,用来表示当所有的前置任务中,有任意一个任务完成(包括异
          • 常)的阶段。可以在此阶段后执行CompletableFuture的其他操作。
      • 所有任务都完成后--allOf
          • 与anyOf类似,allOf静态方法表示传入的所有前置任务都完成的阶段。可以在此阶段后执行
          • CompletableFuture的其他操作。
          • 注: 如果异步任务执行异常,也看作是完成状态。
      • 举例
          • 输出结果
    • 异常处理
          • 处理异常的API与其他回调API类似,但触发时机不同而已。
          • exceptionally
          • 作用:当前阶段异常时,以异常为入参执行给定的函数。当前阶段非异常执行完成时,不执行逻辑,方
          • 法返回与当前阶段相同的非异常返回值。
          • 举例
          • 输出结果
  • API的分类与总结
    • 三种基本任务类型
          • 只关心前置任务完成的Runable :这种API以Runable函数式接口为参数,Runable接口的特点是没有入
          • 参,没有返回值。因此这是一种只关系前置任务是否完成的API。对应CompletableFuture中的xxxRun
          • 方法。
          • 关心前置任务结果的消费者Consumer :这种API以Consumer函数式接口为参数,Consumer接口有入
          • 参,没有返回值。因此这是一种关心前置任务结果,但只消费前置结果的API。对应CompletableFuture
          • 中的xxxAccept方法。
          • 关心前置任务的生产者Function :这种API以Function函数传接口为参数,Function接口有入参,有返
          • 回值。因此这是一种可以对前置任务的结果进行操作的API。对应的CompletableFuture中的xxxApply
          • 方法。
          • 特殊的任务
          • 创建任务的Supplier接口与Runnable接口
    • 三种重载方法
          • CompletableFuture描述任务关系的api中,同一个功能的方法大都提供了三个重载方法。类似:xxx,
          • xxxAsync,xxxAsync(...Executor executor)。三个方法的区别如下:
          • xxx方法,表示当前任务不会主动提交到线程池中执行。
          • xxxAsync方法,表示任务需要在默认的Fork/Join线程池中异步执行。
          • CompletableFuture描述任务关系的api中,同一个功能的方法大都提供了三个重载方法。类似:xxx,
          • xxxAsync,xxxAsync(...Executor executor)。三个方法的区别如下:
          • xxx方法,表示当前任务不会主动提交到线程池中执行。
          • xxxAsync方法,表示任务需要在默认的Fork/Join线程池中异步执行。
          • xxxAsync(...Executor executor)方法,表示任务需要在指定的线程池中异步执行。
    • 分类
          • 基于CompletableFuture描述的任务关系、三种基本的任务类型以及三种重载方法。API大致可以进行如
          • 下的分类:
  • 源码解析
    • 如何创建一个任务
          • 如何封装函数式接口
          • 以supplyAsync为例,一共两个重载方法。都通过调用asyncSupplyStage方法创建任务。
          • asyncSupplyStage的源码逻辑很简单,只是创建了一个AsyncSupply对象,然后提交到线程池中等待执
          • 行。AsyncSupply是ForkJoinTask的子类,并且实现了Runnable接口。因此,相当于向线程池中提交了
          • 一个Runnable接口实现对象。
          • 任务如何被执行的
          • 任务被提交到线程池中后,如果使用默认的ForkJoin线程池,则会调用exec方法执行线程池中的任务。
          • 如果是Executor的线程池实现,则会调用run方法执行任务。因此,任务时通过exec或run方法被线程池
          • 触发执行的。
    • thenApply是如何执行的
          • 调用CompletableFuture对象thenApplyXXX时,最终都在内部调用了uniApplyStage方法,只是参数略
          • 有不同。因此只有理解了uniApplyStage方法,才能理解了thenApply如何工作。
      • uniApplyStage
          • 执行了uniApplyStage方法后,任务有几种状态。
            • }
          • 未执行,放到的前置任务的stack中
          • 已经被直接执行完成
          • 提交到了线程池中执行
    • 封装函数是如何被执行的
      • 函数式接口在哪里被调用
      • UniApply源码解析
    • 后续任务是如何被调用的
          • CompletableFuture中,有些任务不会立即被执行。而是被放到了前置任务的stack中,那么这些任务是
          • 何时以及如何被触发的呢?
      • 调用者触发执行
      • 前置任务触发
          • 前置任务执行完成后,依次执行stack中的任务,任务通过tryFire方法被执行。tryFire方法执行完成后,
          • 调用任务对象的postFire方法,该方法会保证前置阶段与当前阶段的后续任务依次被调用。
          • UniApply#tyrFire
      • postFire方法
          • ”a“未完成的情况
    • 后续任务的后续任务是如何被执行的
          • CompletableFuture中的依赖链并不是单向的一条直线。stack栈中的任务,也有着代表着自己的执行阶
          • 段的CompletableFuture对象,这些对象也有自己的stack栈。因此一个CompletableFuture对象的后续
          • 任务调用链是可以由无数多的分支的。
          • 保证这些任务被正确执行的关键在于postComplete方法。
          • ![[CP任务依赖关系.png]]
      • postComplete源码分析
      • stack整理流程图
          • 初始状态
          • 执行stack中的第一个任务
          • 执行完成,开始压入stack
          • stack整理完成
    • allOf、anyOf是如何实现的
          • 假设有一个任务Z,依赖一组任务[A, B, C, D, E, F, G, H]
          • 对于allOf, 当这组任务都完成时,才会执行Z;对于anyOf, 当这组任务中有任何一个完成,就执行任务
          • Z。
          • 1. 一种方法是通过isDone方法不断的轮询数组中的对象,直到满足条件
          • 2. 另一种针对allOf的实现,可以依次调用每个任务的get方法,直到所有的任务都完成。
          • 而CompletableFuture提供了更优雅的解决方式,即将原本的数组通过中间对象转变为树结构。因此每
          • 个任务都相当于CompletableFuture中的xxxBoth或xxxEither。
          • 对于allOf, Z只要保证Z1和Z2都完成了就行,Z1和Z2分别保证Z11,Z12 和 Z21,Z22都完成了就像,而
          • Z11,Z12,Z21,Z22则分别保证了A-H任务都完成。
          • 对应anyOf, Z 只要保证Z1和Z2有一个完成了就像,Z1和Z2联合保证了Z11,Z12,Z21,Z22这 4 个任务只要
          • 有一个完成了就行,同理,Z11,Z12,Z21,Z22则联合保证了A-H中有一个任务完成了就行。
          • “Z”可以理解为allOf或anyOf返回的CompletableFuture对象,我们可以基于此对象继续使用
          • CompletableFuture的API编写后续的逻辑。

CompletableFuture与Future有什么不同
开发背景
对比举例
Future实现
CompletableFuture实现
不同点
相同点
CompletableFuture的基本使用
创建一个异步任务
获取任务结果
任务完成后的回调
thenApplyXXX
thenAcceptXXXX
thenRunXXX
whenCompleteXXX
handleXXX
thenComposeXXX
依赖于两个任务的回调
前置两个任务都完成后
thenCombineXXX
thenAcceptBothXXX
runAfterBothXXX
前置任意一个任务完成后
applyToEitherXXX
acceptEitherXXX
runAfterEitherXXX
举例
依赖于多个任务的回调
任意一个完成后–anyOf
所有任务都完成后–allOf
举例
异常处理
API的分类与总结
三种基本任务类型
三种重载方法
分类
源码解析
如何创建一个任务
thenApply是如何执行的
uniApplyStage
封装函数是如何被执行的
函数式接口在哪里被调用
UniApply源码解析

后续任务是如何被调用的
调用者触发执行
前置任务触发
postFire方法

后续任务的后续任务是如何被执行的
postComplete源码分析
stack整理流程图
allOf、anyOf是如何实现的

CompletableFuture与Future有什么不同

开发背景
在使用多线程处理任务时,经常会需要等待某一阶段的任务执行完成之后,根据阶段结果再开启新的异
步任务。
这个逻辑可以使用Future来实现,通过Future的isDone方法或get方法来判断异步任务是否完成或获取
结果。这种方法的问题在于,isDone轮询会消耗CPU资源,并且不能够及时的获取的任务完成的状态;
get方法会使调用的线程阻塞,无法处理后续逻辑。
CompletableFuture提供了回调机制和丰富的Stream API,可以很好的完成异步任务的回调和后续任务
处理。并且CompletableFuture可以很好的描述各个异步任务之间的关系,让逻辑更清晰直观。内部的
无锁并发栈也能够高性能的完成异步任务之间的并行调用。
对比举例
模拟场景:
小白去餐厅吃饭,点了一份西红柿鸡蛋和一碗米饭。点完之后就玩王者等吃饭。
此时服务员开始做米饭,厨师开始炒菜。做好之后由服务员打饭上菜,叫小白吃饭。
Future实现
@Test
public void FutureExample2() throws ExecutionException, InterruptedException
{
ExecutorService waiters = Executors.newFixedThreadPool( 2 );
ExecutorService cookers = Executors.newFixedThreadPool( 2 );
printTimeAndThread("小白进入餐厅");
printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
Future makeRice = waiters.submit(() -> {
ThreadTools.sleepSeconds( 2 );
printTimeAndThread("饭好了");
return "米饭";
});
Future cookDish = cookers.submit(() -> {
ThreadTools.sleepSeconds( 2 );
printTimeAndThread("菜好了");
return "番茄炒蛋";
});
Future getRice = waiters.submit(() -> {
final long start = System.currentTimeMillis();
String rice = makeRice.get();
String dish = cookDish.get();
ThreadTools. sleepSeconds( 2 );
printTimeAndThread(String.format("服务员打饭完成,耗时%d",
System.currentTimeMillis() - start));
return String.format("%s + %s 好了", dish, rice);
});
printTimeAndThread("小白在打王者");
printTimeAndThread(String.format("%s ,小白开吃", getRice.get()));
}
结果
CompletableFuture实现
结果
1634092703854 | main | 小白进入餐厅
1634092703854 | main | 小白点了 番茄炒蛋 + 一碗米饭
1634092703856 | main | 小白在打王者
1634092705874 | pool-2-thread-1 | 菜好了
1634092705874 | pool-1-thread-1 | 饭好了
1634092707884 | pool-1-thread-2 | 服务员打饭完成,耗时 4028
1634092707885 | main | 番茄炒蛋 + 米饭 好了 ,小白开吃
@Test
public void CPExample2() {
printTimeAndThread("小白进入餐厅");
printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
CompletableFuture makeRice = CompletableFuture.supplyAsync(() ->
{
sleepSeconds( 2 );
FutureTaskTest.printTimeAndThread("做米饭");
return "米饭";
});
CompletableFuture makeDish = CompletableFuture.supplyAsync(() ->
{
sleepSeconds( 2 );
printTimeAndThread("厨师炒菜");
return "番茄炒蛋";
});
final CompletableFuture allDone = makeDish.thenCombine(makeRice,
(dish, rice) -> {
final long start = System.currentTimeMillis();
sleepSeconds( 2 );
printTimeAndThread(String.format("服务员打饭完成,耗时%d",
System.currentTimeMillis() - start));
return String.format("%s + %s 好了", dish, rice);
});
printTimeAndThread("小白在打王者");
printTimeAndThread(String.format("%s ,小白开吃", allDone.join()));
}
1634822712205 | main | 小白进入餐厅
1634822712205 | main | 小白点了 番茄炒蛋 + 一碗米饭
1634822712207 | main | 小白在打王者
1634822714221 | ForkJoinPool.commonPool-worker-2 | 厨师炒菜
1634822714221 | ForkJoinPool.commonPool-worker-9 | 做米饭
1634822716229 | ForkJoinPool.commonPool-worker-9 | 服务员打饭完成,耗时 2008
1634822716229 | main | 番茄炒蛋 + 米饭 好了 ,小白开吃
不同点
1. CompletableFuture是在前两个任务完成后,自动调用后续任务的;而Future则阻塞了一个线程,
用来等待前两个任务完成。
2. CompletableFuture获取结果的join方法抛出的是RuntimeException,而get方法抛出的是受检异
常InterruptedException, ExecutionException
3. Future需要显示的指定线程池,而CompletableFuture可以默认使用ForkJoinPool。
4. CompletableFuture可以使用类似Stream表达式的API表现出任务之间的逻辑关系,而Future不能
在语法上直观的表示出这个关系。
相同点
对于逻辑上需要获取异步线程计算结果的任务,二者都可能在获取结果时被阻塞。

CompletableFuture的基本使用

CompletableFuture提供的api,大概可以描述一下几种场景:
1. 创建一个异步任务
2. 获取任务的结果
3. 在一个异步任务完成后执行某个逻辑
4. 在两个任务都完成后执行某个逻辑
5. 在两个任务任意一个完成后执行某个逻辑
6. 任意多个任务都完成后执行某个逻辑
7. 任意多个任务,其中一个完成后执行某个逻辑

创建一个异步任务

CompletableFuture提供了两个API用于开启一个异步任务。分别是使用Runnable接口对象作为参数的
runAsync方法与使用Supplier接口对象对作为参数的supplyAsync方法。两种API的区别就是Runnable
接口与Supplier接口的区别,即runAsync方法没有返回值,而supplyAsync方法有返回值。
两个方法使用默认的ForkJoinPool作为线程池执行异步任务。如果需要指定其他的线程池,可以使用以
下重载API:
使用举例:
public static CompletableFuture runAsync(Runnable runnable)
public static  CompletableFuture supplyAsync(Supplier supplier)
public static CompletableFuture runAsync(Runnable runnable,Executor
executor)
public static  CompletableFuture supplyAsync(Supplier supplier,Executor
executor)
执行结果:

获取任务结果

与Future的区别
CompletableFuture比Future多提供了两种获取结果的方法:getNow与Join。
1. getNow类似于Java8的Stream中的optional.orElse()方法,当结果不存在或没有完成时返回给定的
结果。
2. join与get类似,都会阻塞线程,二者区别在于抛出的异常不同。join可能抛出unchecked
exception,不需要强制try catch。get在方法签名中声明了受检异常,必须要在编码时进行处理。
使用举例
@Test
public void CompletableFutureStart() {
CompletableFuture.runAsync(() -> {
ThreadTools.sleepSeconds( 1 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
});
CompletableFuture.supplyAsync(() -> {
ThreadTools.sleepSeconds( 2 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
return "success";
});
ThreadTools.sleepSeconds( 5 );
}
run on ForkJoinPool.commonPool-worker- 1
run on ForkJoinPool.commonPool-worker- 2
public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
public T join()
@Test
public void CompletableFutureGet() {
try {
final Void unused = CompletableFuture.runAsync(() -> {
ThreadTools.sleepSeconds( 1 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
}).get();
System.out.println("CompletableFuture task 1 result "+unused);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
final String result = CompletableFuture.supplyAsync(() -> {
ThreadTools.sleepSeconds( 2 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
return "success";
}).join();
System.out.println("CompletableFuture task2 result "+result);
ThreadTools.sleepSeconds( 5 );
结果

任务完成后的回调

CompletableFuture的一大特性就是丰富的基于回调的API。当某一个阶段的任务完成后,有时希望能够
及时的获取结果,并操作下一步的逻辑。如果使用Future来实现,那么可能需要阻塞的通过get获取结
果,或不断的通过isDone方法轮询,确认任务是否完成。但使用CompletableFuture,这一切就可以很
自然并高效的完成。并且CompletableFuture的回调对象可以是多个,多个回调对象会被依次回调执
行。
针对与某“一个”任务完成的回调,有多个API可以调用。这些API的区别基本在于入参与返回值,但执行
的时机都是当前阶段的任务完成后。具体如下:
thenApplyXXX
作用:当前阶段(调用该方法的CompletionStage对象)非异常完成时,以 当前阶段的结果为入参 ,执
行给定的函数,函数有返回值。
举例
thenAcceptXXXX
作用:与thenApply类似。当前阶段非异常完成时, 以当前结果为入参 ,执行给定的consumer函数, 函
数没有返回值 。方法返回CompletionStage对象,对象的阶段结果为null。
}
run on ForkJoinPool.commonPool-worker-
CompletableFuture task 1 result null
run on ForkJoinPool.commonPool-worker-
CompletableFuture task2 result success
public  CompletionStage thenApply(Function fn);
public  CompletionStage thenApplyAsync(Function
fn);
public  CompletionStage thenApplyAsync(Function
fn,Executor executor);
@Test
public void RunTest() {
final CompletableFuture future1 = CompletableFuture.supplyAsync(() -
> "from supply").thenApply(r -> {
System.out.println(r);
return "success";
});
System.out.println(future1.join());
}
public CompletionStage thenAccept(Consumer action);
public CompletionStage thenAcceptAsync(Consumer action);
public CompletionStage thenAcceptAsync(Consumer
action,Executor executor);
thenRunXXX
作用:当前阶段非异常完成时,执行制定动作。 没有入参和返回值 。
whenCompleteXXX
作用:当前阶段完成时给定的BiConsumer函数被调用, 入参为抛出的异常(正常结束时为null)和结
果(异常结束时为null) , 函数无返回值 ,方法返回的CompletionStage对象,含的结果或异常与当前
阶段相同(不改变上一阶段的结果)。
特点:可同时处理正常与异常的情况。
handleXXX
作用:当前阶段完成时给定的BiFunction函数被调用, 入参为抛出的异常(正常结束时为null)和结果
(异常结束时为null) , 函数有返回值 。返回的CompletionStage对象为函数式接口的结果包装。
thenComposeXXX
作用:当前阶段非异常完成时,执行给定的函数。函数使用当前阶段的结果作为入参, 函数 返回另一个
CompletionStage对象。thenCompose方法会将这个对象展开,避免结果的嵌套。执行效果类似于
Stream中的flatMap。
举例:
public CompletionStage thenRun(Runnable action);
public CompletionStage thenRunAsync(Runnable action);
public CompletionStage thenRunAsync(Runnable action,Executor executor);
public CompletionStage whenComplete(BiConsumer
action);
public CompletionStage whenCompleteAsync(BiConsumer action);
public CompletionStage whenCompleteAsync(BiConsumer action,Executor executor);
public  CompletionStage handle(BiFunction fn);
public  CompletionStage handleAsync(BiFunction fn);
public  CompletionStage handleAsync(BiFunction fn,Executor executor);
public  CompletionStage thenCompose(Function> fn);
public  CompletionStage thenComposeAsync(Function> fn);
public  CompletionStage thenComposeAsync(Function> fn,Executor executor);
/**
* thenCompose举例
*/
@Test
thenCompose结果输出
thenApply结果输出

依赖于两个任务的回调

有时后续的任务可能需要依赖于前两个任务的结果,针对这种情况,CompletableFuture也提供了对应
的API。分为两类,分别是前两个任务都完成,前两个任务任意一个完成后,调用后续任务执行逻辑。具
体如下:
public void FutureTaskTestCompose() {
CompletableFuture future1 = CompletableFuture.supplyAsync(() ->
"task 1");
final CompletableFuture future2 =
CompletableFuture.supplyAsync(() -> "next stage");
CompletableFuture nextFuture = future1.thenCompose((result) -> {
System.out.printf("future1 result is \"%s\"%n", result);
return future2;
});
System.out.println(future2 == nextFuture);
final String join = nextFuture.join();
System.out.printf("nextFuture result is \"%s\"%n", join);
}
/**
* thenCompose对比thenApply
*/
@Test
public void FutureTaskTestCompose2() {
CompletableFuture future1 = CompletableFuture.supplyAsync(() ->
"task 1");
final CompletableFuture future2 =
CompletableFuture.supplyAsync(() -> "next stage");
//thenApply返回一个CompletableFuture对象
final CompletableFuture> nextFuture =
future1.thenApply((result) -> {
System.out.printf("future1 result is \"%s\"%n", result);
return future2;
});
final CompletableFuture innerStage = nextFuture.join();//获取到的
结果为CompletableFuture对象
final String result = innerStage.join();//获取返回的CompletableFuture对象执
行的结果
System.out.printf("nextFuture result is \"%s\"%n", result);
}
future1 result is "task 1"
false
nextFuture result is "next stage"
future1 result is "task 1"
nextFuture result is "next stage"

前置两个任务都完成后

thenCombineXXX
当调用该方法的前置任务与给点的前置任务都完成时,执行BiFunction函数。 函数的入参为前置两阶段
的返回结果,函数无返回值。 阶段的结果CompletableFuture对象中result字段值为函数计算结果。
thenAcceptBothXXX
作用:与thenAccep类似,但依赖于两个阶段。当前阶段与给定的阶段都非异常的执行完成时, 以两个
阶段的结果为入参 ,执行给定的函数, 函数无返回值 。阶段的结果CompletableFuture对象中result字
段值为null。
runAfterBothXXX
作用:与thenRun类似,当前阶段与给点的阶段都异常的完成时,执行给点的函数。 函数无入参,无返
回值 。阶段的结果CompletableFuture对象中result字段值为null。

前置任意一个任务完成后

applyToEitherXXX
作用:当前阶段或给点的阶段任意一个非异常的完成时,执行给定的函数。 以完成阶段的结果作为函数
的入参,函数有返回值。 阶段的结果CompletableFuture对象中result字段值为函数计算结果。
public  CompletionStage thenCombine(CompletionStage
other,BiFunction fn);
public  CompletionStage thenCombineAsync (CompletionStage
other, BiFunction fn);
public  CompletionStage thenCombineAsync (CompletionStage
other, BiFunction fn,Executor executor);
public  CompletionStage thenAcceptBoth(CompletionStage
other,BiConsumer action);
public  CompletionStage thenAcceptBothAsync(CompletionStage other,BiConsumer action);
public  CompletionStage thenAcceptBothAsync(CompletionStage other,BiConsumer action, Executor executor);
public CompletionStage runAfterBoth(CompletionStage other,Runnable
action);
public CompletionStage runAfterBothAsync(CompletionStage other,Runnable
action);
public CompletionStage runAfterBothAsync(CompletionStage other,Runnable
action,Executor executor);
public  CompletionStage applyToEither(CompletionStage
other,Function fn);
public  CompletionStage applyToEitherAsync(CompletionStage
other,Function fn);
public  CompletionStage applyToEitherAsync(CompletionStage
other,Function fn,Executor xecutor);
acceptEitherXXX
作用:同applyToEither。但函数为consumer函数式接口, 无返回值 。
runAfterEitherXXX
作用:同applyToEither。但函数为Runnable接口, 无入参,无返回值。

举例

输出结果
public CompletionStage acceptEither(CompletionStage
other,Consumer action);
public CompletionStage acceptEitherAsync(CompletionStage
other,Consumer action);
public CompletionStage acceptEitherAsync(CompletionStage
other,Consumer action,Executor executor);
public CompletionStage runAfterEither(CompletionStage other,Runnable
action);
public CompletionStage runAfterEitherAsync(CompletionStage
other,Runnable action);
public CompletionStage runAfterEitherAsync(CompletionStage
other,Runnable action,Executor executor);
/**
* 依赖于两个前置任务的API举例
*/
@Test
public void bothOrAny() {
final CompletableFuture task1 = CompletableFuture.supplyAsync(() ->
{
ThreadTools.sleepSeconds( 1 );
return "task 1";
});
final CompletableFuture task2 = CompletableFuture.supplyAsync(() ->
{
ThreadTools.sleepSeconds( 2 );
return "task 2";
});
//依赖于两个前置任务都完成
final CompletableFuture both = task2.thenAcceptBothAsync(task1, (r1,
r2) -> {
System.out.printf("两个任务都已经完成。结果 1 :%s ,结果2: %s%n", r1, r2);
});
//依赖于两个前置任务,但有一个完成即可
final CompletableFuture any = task1.acceptEitherAsync(task2, (r) -> {
System.out.printf("有一个任务已经完成,结果是:%s%n", r);
});
//阻塞主线程结束
any.runAfterBoth(both, ()->{}).join();
}

依赖于多个任务的回调

除了依赖于一个、两个前置任务的API,更常用的是基于批量任务完成时机的判断。CompletableFuture
也提供了对应的api。

任意一个完成后–anyOf

CompletableFuture提供了静态方法,用来表示当所有的前置任务中,有任意一个任务完成(包括异
常)的阶段。可以在此阶段后执行CompletableFuture的其他操作。

所有任务都完成后–allOf

与anyOf类似,allOf静态方法表示传入的所有前置任务都完成的阶段。可以在此阶段后执行
CompletableFuture的其他操作。
注: 如果异步任务执行异常,也看作是完成状态。

举例

输出结果
有一个任务已经完成,结果是:task 1
两个任务都已经完成。结果 1 :task 2 ,结果2: task 1
public static CompletableFuture anyOf(CompletableFuture... cfs)
 
  
public static CompletableFuture allOf(CompletableFuture... cfs)
@Test
public void anyANdAll() {
final ExecutorService threadPool = Executors.newFixedThreadPool( 10 );
final long startAll = System.currentTimeMillis();
final CompletableFuture[] completableFutures = IntStream.rangeClosed( 1 ,
10 ).mapToObj(index -> CompletableFuture.supplyAsync(() -> {
final long start = System.currentTimeMillis();
try {
Thread.sleep( 100 * index);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("线程'%s'执行完成,花费%d毫秒%n",
Thread.currentThread().getName(), System.currentTimeMillis() - start);
return index;
}, threadPool)).toArray(CompletableFuture[]::new);
final Object result = CompletableFuture.anyOf(completableFutures).join();
System.out.printf("any of 得到结果:%s,共花费%d毫秒%n", result,
System.currentTimeMillis() - startAll);
CompletableFuture.allOf(completableFutures).join();//阻塞主线程
System.out.printf("all of 得到结果,共花费%d毫秒%n", System.currentTimeMillis()
  • startAll);
    }

异常处理

处理异常的API与其他回调API类似,但触发时机不同而已。
exceptionally
作用:当前阶段异常时,以异常为入参执行给定的函数。当前阶段非异常执行完成时,不执行逻辑,方
法返回与当前阶段相同的非异常返回值。
举例
输出结果

API的分类与总结

线程'pool-1-thread-1'执行完成,花费 110 毫秒
any of 得到结果: 1 ,共花费 122 毫秒
线程'pool-1-thread-2'执行完成,花费 205 毫秒
线程'pool-1-thread-3'执行完成,花费 300 毫秒
线程'pool-1-thread-4'执行完成,花费 411 毫秒
线程'pool-1-thread-5'执行完成,花费 505 毫秒
线程'pool-1-thread-6'执行完成,花费 615 毫秒
线程'pool-1-thread-7'执行完成,花费 710 毫秒
线程'pool-1-thread-8'执行完成,花费 804 毫秒
线程'pool-1-thread-9'执行完成,花费 915 毫秒
线程'pool-1-thread-10'执行完成,花费 1011 毫秒
all of 得到结果,共花费 1024 毫秒
public CompletionStage exceptionally(Function fn);
@Test
public void CPExceptional() {
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("抛出的异常");
}).exceptionally((e) -> {
System.out.printf("异常消息%s%n", e.getMessage());
return "new result";
}).thenAccept(r -> {
System.out.printf("最终结果1:%s%n", r);
});
CompletableFuture.supplyAsync(() -> "没有抛出异常")
.exceptionally((e) -> {
System.out.printf("异常消息%s%n", e.getMessage());
return "new result";
}).thenAccept(r ->
System.out.printf("最终结果2:%s%n", r));
}
异常消息java.lang.RuntimeException: 抛出的异常
最终结果1:new result
最终结果2:没有抛出异常

三种基本任务类型

只关心前置任务完成的Runable :这种API以Runable函数式接口为参数,Runable接口的特点是没有入
参,没有返回值。因此这是一种只关系前置任务是否完成的API。对应CompletableFuture中的xxxRun
方法。
关心前置任务结果的消费者Consumer :这种API以Consumer函数式接口为参数,Consumer接口有入
参,没有返回值。因此这是一种关心前置任务结果,但只消费前置结果的API。对应CompletableFuture
中的xxxAccept方法。
关心前置任务的生产者Function :这种API以Function函数传接口为参数,Function接口有入参,有返
回值。因此这是一种可以对前置任务的结果进行操作的API。对应的CompletableFuture中的xxxApply
方法。
特殊的任务
创建任务的Supplier接口与Runnable接口

三种重载方法

CompletableFuture描述任务关系的api中,同一个功能的方法大都提供了三个重载方法。类似:xxx,
xxxAsync,xxxAsync(…Executor executor)。三个方法的区别如下:
xxx方法,表示当前任务不会主动提交到线程池中执行。
xxxAsync方法,表示任务需要在默认的Fork/Join线程池中异步执行。
CompletableFuture描述任务关系的api中,同一个功能的方法大都提供了三个重载方法。类似:xxx,
xxxAsync,xxxAsync(…Executor executor)。三个方法的区别如下:
xxx方法,表示当前任务不会主动提交到线程池中执行。
xxxAsync方法,表示任务需要在默认的Fork/Join线程池中异步执行。
xxxAsync(…Executor executor)方法,表示任务需要在指定的线程池中异步执行。

分类

基于CompletableFuture描述的任务关系、三种基本的任务类型以及三种重载方法。API大致可以进行如
下的分类:

源码解析

如何创建一个任务

如何封装函数式接口
以supplyAsync为例,一共两个重载方法。都通过调用asyncSupplyStage方法创建任务。
asyncSupplyStage的源码逻辑很简单,只是创建了一个AsyncSupply对象,然后提交到线程池中等待执
行。AsyncSupply是ForkJoinTask的子类,并且实现了Runnable接口。因此,相当于向线程池中提交了
一个Runnable接口实现对象。
任务如何被执行的
任务被提交到线程池中后,如果使用默认的ForkJoin线程池,则会调用exec方法执行线程池中的任务。
如果是Executor的线程池实现,则会调用run方法执行任务。因此,任务时通过exec或run方法被线程池
触发执行的。
public static  CompletableFuture supplyAsync(Supplier supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
public static  CompletableFuture supplyAsync(Supplier
supplier,Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
static  CompletableFuture asyncSupplyStage(Executor e,Supplier f) {
if (f == null) throw new NullPointerException();
CompletableFuture d = new CompletableFuture();
e.execute(new AsyncSupply(d, f));//封装Supplier接口并提交到线程池中
return d;
}
static final class AsyncSupply extends ForkJoinTask
implements Runnable, AsynchronousCompletionTask {
CompletableFuture dep; Supplier fn;
AsyncSupply(CompletableFuture dep, Supplier fn) {
this.dep = dep; this.fn = fn;
}
public final Void getRawResult() { return null; }
public final void setRawResult(Void v) {}
public final boolean exec() { run(); return true; }
public void run() {
CompletableFuture d; Supplier f;
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
if (d.result == null) {
try {
d.completeValue(f.get());//执行supply函数的get方法
} catch (Throwable ex) {
d.completeThrowable(ex);
}

thenApply是如何执行的

调用CompletableFuture对象thenApplyXXX时,最终都在内部调用了uniApplyStage方法,只是参数略
有不同。因此只有理解了uniApplyStage方法,才能理解了thenApply如何工作。

uniApplyStage

执行了uniApplyStage方法后,任务有几种状态。
}
d.postComplete();//调用后续任务
}
}
}
private  CompletableFuture uniApplyStage(
Executor e, Function f) {
if (f == null) throw new NullPointerException();
CompletableFuture d = new CompletableFuture();
if (e != null || !d.uniApply(this, f, null)) {//判断线程池是否为空,为空则直接尝试
执行
//线程池不为空,或前置任务未完成,需要入栈排队。
UniApply c = new UniApply(e, d, this, f);//新建对象,封装代表执行逻
辑的函数式接口对象f,代表当前阶段的CP对象d,还有前置任务this,以及线程池e;
push(c);//将封装好的任务push到当前CP对象的stack中
c.tryFire(SYNC);//防止push过程中前置任务变更完成状态,漏掉当前阶段的任务。尝试执行
一次。
}
return d;
}
未执行,放到的前置任务的stack中
已经被直接执行完成
提交到了线程池中执行

封装函数是如何被执行的

函数式接口在哪里被调用

UniApply源码解析

后续任务是如何被调用的

CompletableFuture中,有些任务不会立即被执行。而是被放到了前置任务的stack中,那么这些任务是
何时以及如何被触发的呢?

调用者触发执行

final  boolean uniApply(CompletableFuture a,
Function f,
UniApply c) {//a前置CP,f当前阶段函数,c封装当前阶
段逻辑的Completion对象
Object r; Throwable x;
if (a == null || (r = a.result) == null || f == null)
return false;//前置任务未完成或其他异常情况
tryComplete: if (result == null) {//当前CP的结果为空
if (r instanceof AltResult) {
if ((x = ((AltResult)r).ex) != null) {
completeThrowable(x, r);//之前任务结果为异常
break tryComplete;
}
r = null;//前置任务结果为空,decode为null
}
try {
if (c != null && !c.claim())//claim判断任务是否被执行过;通过“调用线
程”执行c才是null
return false;
@SuppressWarnings("unchecked") S s = (S) r;
completeValue(f.apply(s));//调用function函数的apply方法,并将结果封装
到CompletableFuture对象中。
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;
}

前置任务触发

前置任务执行完成后,依次执行stack中的任务,任务通过tryFire方法被执行。tryFire方法执行完成后,
调用任务对象的postFire方法,该方法会保证前置阶段与当前阶段的后续任务依次被调用。
UniApply#tyrFire

postFire方法

”a“未完成的情况
private  CompletableFuture uniApplyStage(
Executor e, Function f) {
if (f == null) throw new NullPointerException();
CompletableFuture d = new CompletableFuture();
if (e != null || !d.uniApply(this, f, null)) {
UniApply c = new UniApply(e, d, this, f);
push(c);
c.tryFire(SYNC);//前一个阶段已经完成,当前阶段"自己"触发任务执行
}
return d;
}
final CompletableFuture tryFire(int mode) {
CompletableFuture d; CompletableFuture a;
if ((d = dep) == null ||
!d.uniApply(a = src, fn, mode > 0? null : this))//执行当前阶段任务逻辑
return null;
dep = null; src = null; fn = null;
return d.postFire(a, mode);//清理stack、调用后续任务
final CompletableFuture postFire(CompletableFuture a, int mode) {
if (a != null && a.stack != null) {
if (mode < 0 || a.result == null)//嵌套调用时,清理后续无效节点;非嵌套调用
时,当前阶段已经完成,前置阶段未完成(xxxEither),把当前节点从a的stack中清理出去。见下面例
子。
a.cleanStack();//清理stack中的无效节点
else
a.postComplete();//(非嵌套调用,前置任务a已经完成。可能是异步线程执行完毕
的回调)如果当前阶段任务执行完成,前置任务已经完成,前置任务的stack不为空,使用当前线程帮助前置
任务执行stack
}
if (result != null && stack != null) {
if (mode < 0 )
return this;//嵌套调用,返回当前阶段。
else
postComplete();//非嵌套调用,处理当前阶段的stack
}
return null;
}

后续任务的后续任务是如何被执行的

CompletableFuture中的依赖链并不是单向的一条直线。stack栈中的任务,也有着代表着自己的执行阶
段的CompletableFuture对象,这些对象也有自己的stack栈。因此一个CompletableFuture对象的后续
任务调用链是可以由无数多的分支的。
保证这些任务被正确执行的关键在于postComplete方法。
![[CP任务依赖关系.png]]

postComplete源码分析

CompletableFuture future1 = CompletableFuture.
supplyAsync(() -> {
System.out.println("future1执行完成");
return null;
});
CompletableFuture future2 = CompletableFuture.supplyAsync(() ->
{
ThreadTools.sleepSeconds( 1 );
System.out.println("future2 执行完成");
return "future2 result";
});
future2.thenRun(System.out::println);
future2.acceptEitherAsync(future1, (r) -> System.out.println()).join();
final void postComplete() {
/*
* On each step, variable f holds current dependents to pop
* and run. It is extended along only one path at a time,
* pushing others to avoid unbounded recursion.
* f-->当前CP对象,h-->CP对象的stack,t-->stack的next节点
*/
CompletableFuture f = this; Completion h;
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
CompletableFuture d; Completion t;
if (f.casStack(h, t = h.next)) {//保证postComplete被并发调用时,同一个任务只能
被一个线程拿到
if (t != null) {
if (f != this) {//下一阶段CP对象的stack不为空,将stack压入当前CP对象的
stack中。防止递归调用过深。
pushStack(h);
continue;
}
h.next = null;  // detach
}
f = (d = h.tryFire(NESTED)) == null? this : d;//d-->tryFire的返回值;
d不为空时,f被指向d。即h任务所在阶段的CompletableFuture对象
}
}
}

stack整理流程图

初始状态
执行stack中的第一个任务
执行完成,开始压入stack
stack整理完成

allOf、anyOf是如何实现的

假设有一个任务Z,依赖一组任务[A, B, C, D, E, F, G, H]
对于allOf, 当这组任务都完成时,才会执行Z;对于anyOf, 当这组任务中有任何一个完成,就执行任务
Z。
1. 一种方法是通过isDone方法不断的轮询数组中的对象,直到满足条件
2. 另一种针对allOf的实现,可以依次调用每个任务的get方法,直到所有的任务都完成。
而CompletableFuture提供了更优雅的解决方式,即将原本的数组通过中间对象转变为树结构。因此每
个任务都相当于CompletableFuture中的xxxBoth或xxxEither。
对于allOf, Z只要保证Z1和Z2都完成了就行,Z1和Z2分别保证Z11,Z12 和 Z21,Z22都完成了就像,而
Z11,Z12,Z21,Z22则分别保证了A-H任务都完成。
对应anyOf, Z 只要保证Z1和Z2有一个完成了就像,Z1和Z2联合保证了Z11,Z12,Z21,Z22这 4 个任务只要
有一个完成了就行,同理,Z11,Z12,Z21,Z22则联合保证了A-H中有一个任务完成了就行。
“Z”可以理解为allOf或anyOf返回的CompletableFuture对象,我们可以基于此对象继续使用
CompletableFuture的API编写后续的逻辑。

你可能感兴趣的:(java)