深入解析CompletableFuture源码实现(3)———多源输入

2025-12-12 0 804

前言

CompletableFuture(CF) 提供了一种灵活的方式来处理异步计算。通过其丰富的 API,开发者可以轻松地组合多个异步任务。然而,其内部实现涉及复杂的状态管理和线程安全机制。本文将通过源码解析,揭示 CompletableFuture 的内部工作原理。

上一篇文章深入解析CompletableFuture源码实现(2)———双源输入 中我们分析了thenCombine的实现,这篇文章中将继续对多源输入进行分析。

五、多输入 allOf 源码分析

回调数据类 BiRelay 和回调二叉树的构造

我们看下 allOf 专用的回调 Completion: BiRelay。其作用是:传递两个源CF结束回调到依赖CF,依赖CF只存 null 或者异常。虽然从技术角度也可以存所有结果,不过道哥似乎没有这方面的想法。

static final class BiRelay extends BiCompletion { // for And
    BiRelay(CompletableFuture dep,
            CompletableFuture src, CompletableFuture snd) {
        super(null, dep, src, snd);
    }
    final CompletableFuture tryFire(int mode) {
        CompletableFuture d;
        CompletableFuture a;
        CompletableFuture b;
        Object r, s, z; Throwable x;
        if (   (a = src) == null || (r = a.result) == null
            || (b = snd) == null || (s = b.result) == null
            || (d = dep) == null)
            // 只有一个源有结果时,必然返回null
            return null;
        if (d.result == null) {
            if ((r instanceof AltResult
                 && (x = ((AltResult)(z = r)).ex) != null) ||
                (s instanceof AltResult
                 && (x = ((AltResult)(z = s)).ex) != null))
                 // 有异常,则存异常
                d.completeThrowable(x, z);
            else
                // 双源输入,无异常,则存null,表示两个源均已完成
                d.completeNull();
        }
        // 清理资源。这里的思想是:如果已经得到计算结果,则清理回调数据类内数据(源、回调等)
        src = null; snd = null; dep = null;
        // 继续清理资源,清理a和b的回调栈
        return d.postFire(a, b, mode);
    }
}

这里的清理实现可以先不关注,不影响对于整体实现的理解:

final CompletableFuture postFire(CompletableFuture a,
                                    CompletableFuture b, int mode) {
    // 1. 先清理第二个栈
    if (b != null && b.stack != null) { // clean second source
        Object r;
        if ((r = b.result) == null)
            // 需要注意,这里result == null表示未完成状态,此时必然存在多余的还未执行的BiRelay回调
            // 清理栈
            b.cleanStack();
        if (mode >= 0 && (r != null || b.result != null))
            b.postComplete();
    }
    // 2. 清理第一个栈
    return postFire(a, mode);
}

构建二叉树

之前我们说过BiCompletion保存了双源CF和依赖CF,可以想到一个最简单的实践是,反复使用 thenCombine,最终保存所有的输入,对应的代码是:

cfs.stream().reduce((a, b) -> a.thenCombine(b, (x, y) -> null));

如果这样实现的话allOf仅仅是对原有功能的复用,但是效率很差。出现异常结束的结果时,时间复杂度为o(n)。如果最后一个cf得到异常结果,需要走完所有 n-1 个 BiApply(Completion)回调,才能得到最终结果。

public static CompletableFuture allOf(CompletableFuture... cfs) {
    return andTree(cfs, 0, cfs.length - 1);
}

递归构建二叉树:

static CompletableFuture andTree(CompletableFuture[] cfs,
                                       int lo, int hi) {
    // 创建父结点
    CompletableFuture d = new CompletableFuture();
    if (lo > hi) // empty
        d.result = NIL;
    else {
        // a,b分别为左右节点
        CompletableFuture a, b; Object r, s, z; Throwable x;
        int mid = (lo + hi) >>> 1;
        if ((a = (lo == mid ? cfs[lo] :
                  andTree(cfs, lo, mid))) == null ||
            (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] :
                  andTree(cfs, mid+1, hi))) == null)
            throw new NullPointerException();
        if ((r = a.result) == null || (s = b.result) == null)
            // a,b均未完成时,回调入栈,a和b都要入栈
            // a和b对于回调的调用共两次,没有异常的话,则第二次回调完成当前头结点
            a.bipush(b, new BiRelay(d, a, b));
        // 后面两个else分支分别处理了已知异常和已知无异常结果
        else if ((r instanceof AltResult
                  && (x = ((AltResult)(z = r)).ex) != null) ||
                 (s instanceof AltResult
                  && (x = ((AltResult)(z = s)).ex) != null))
            
            d.result = encodeThrowable(x, z);
        else
            d.result = NIL;
    }
    return d;
}

这是一个递归实现,左右节点的构造依然调用当前函数,只不过参数范围缩小了。
关于递归实现一个重要的技巧是:先看方法输出,理解输出是什么,然后不要进入“深度优先搜索”的陷阱,要先理解当前函数的实现。这里输出是二叉树的父节点,左节点和右节点通过 Completion 回调通知父节点。

再来看下参数范围,lo和hi表示左右区间坐标。
由于 mid = (lo + hi) >>> 1,所以其指向的坐标偏左,也就是说,如果有两个元素(lo + 1 = hi),mid 指向lo。

如果坐标的长度为0,父节点可以直接返回完成值,即 CF(null),其可以作为其他节点的子节点,满足 identity 性质。
如果坐标的长度为1,左节点对应于cfs[lo],右节点对应于上一个例子。
如果坐标的长度为2,左右节点分别对应cfs[lo],cfs[hi],可以构建隐形的链接BiApply。
如果坐标的长度为3,左节点对应于上一个例子,右节点对应于cfs[hi]。
以此类推,最终构造了一个完全二叉树。

图解

当 n = 4时,构造如图:

说明:d0_3表示构造的节点,区间为[0,3]。单个CFi实际应该表示为左节点cfs[i]和右节点CF(null),为便于理解没有完全画出。

                 d0_3 (allOf 结果)
                 /      
                /        
    d0_1 (CF0,CF1 的组合) d2_3 (CF2,CF3 的组合)
          /            /    
         /            /      
       CF0      CF1    CF2      CF3
               

n = 5时,构造如图:

                              CF_0_4
                             /      
                            /        
                     CF_0_2          CF_3_4
                    /              /      
                   /              /        
              CF_0_1        CF2    CF3        CF4
             /      
            /        
          CF0        CF1

显然,当任意一个叶子节点以异常完成时,allOf计算的时间复杂度为o(logn)。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 深入解析CompletableFuture源码实现(3)———多源输入 https://www.zuozi.net/35792.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务