接上篇,本篇继续介绍CompletableFuture的高阶使用方式。
1.举例
1)supplyAsync配合allOf
CompletableFuture的supplyAsync配合allOf,将所有的future绑定到一起,然后所有都执行完之后触发对应操作,demo:
allOf方法内部调用了andTree方法,看方法注释,该方法会根据传入的参数递归生成一棵树
这段代码可能看起来有点绕,我们举个具体的例子,比如添加5个任务(举例5的原因是因为当任务数为偶数的时候,情况过于简单,奇数情况下任务为3太少,所以就选取了5,5个任务可以覆盖这段代码的每处逻辑),那么cfs是个size为5的数组,lo是0,hi是4,根据这组参数,树的构成逻辑如下:
根据0和4得到mid为2,对a来说,不满足lo和mid相等的条件,所以继续递归调用andTree方法,到下面0和2得到mid为1。对b来说,既不满足lo和hi相等的条件,也不满足hi和mid+1相等的条件,所以也继续递归调用andTree方法,到下面3和4得到mid为3。后续以此来推。经过递归最终生成的树如下图所示:
先解释下这棵树的画法,方形中的三个数字代表andTree方法中的lo,mid和hi,数字i的圆形代表cfs的第i个元素,即cfs[i]。那么根据这棵树,我们将任务两两组合了,一共生成了4个任务:
1)任务0和1组成一对,它们组合在一起起个名字叫做0-1,这里a=cfs[0],b=cfs[1],同时创建了一个叫做d的CompletableFuture,这里叫它d01,作为返回值
2)任务0-1又和任务2组成一对,起个名字叫做0-1-2,这里a=d01,b=cfs[2],同时创建一个CompletableFuture,这里叫它d012,作为返回值
3)任务3和任务4组成一对,叫做3-4,这里a=cfs[3],b=cfs[4],同时创建一个CompletableFuture,这里叫它d34,作为返回值
4)任务0-1-2和任务3-4组成一对,叫做0-1-2-3-4,这里a=d012,b=d34,同时创建一个CompletableFuture,这里叫它d01234。这个d01234就是demo中的最后一个future。
所以:
对1)来说,a=cfs[0],b=cfs[1]都完成之后,会触发d01。
对2)来说,d01和cfs[2]都执行完会触发d012。
对3)来说,a=cfs[3],b=cfs[4]都执行完会触发d34。
对4)来说,d012和d34都执行完之后会触发d01234。
除此之外,allOf得到的最后一个future的触发,是由最晚执行完的那个任务去触发的,从上面的demo中可以看出,打印hello这个任务的线程和最终future任务的线程是同一个线程。
2)supplyAsync配合anyOf
anyOf和allOf的区别,顾名思义,就是allOf是所有的任务都完成后出发最后一个future,anyOf是任何一个任务完成后就触发最后一个future。demo:
它的底层和allOf类似,也是根据传入的任务数量构造一个任务树,anyOf内部会调用orTree,这里和andTree不一样的地方是它内部构造了一个叫做OrRelay的对象,而andTree中构造的是一个叫做BiRelay的对象。那么这两个对象有什么区别呢?直接上源码:
它的tryFire实现里面调用的是orRelay方法,这里面和biRelay的区别如源码所示,orRelay判断两个任务的result都是null的时候才认为任务没有完成,换言之,任何一个任务完成了就去触发剩下的流程。而biRelay判断有任何一个result为空,就认为任务没有结束。
还有一个区别就是biPush方法和orPush方法
biPush是有任何一个任务没完成就压栈。orPush是两个任务都没完成的情况下才压栈。
除此之外,anyOf得到的最后一个future的触发,是由最早执行完的那个任务去触发的,这和allOf正好相反。从上面的demo中可以看出,打印world3这个任务的线程和最终future任务的线程是同一个线程。