Completable 源码解析扩充

链接的文章,只是在我找源码的时候,发现,这篇文章写的很好,分享给大家,也激励自己往这方面努力吧。 Completable别人的教程链接

此篇文章是在分享文章的基础上的扩充,主要分为两个部分,第一部分是算法部分,再次阅读下Completable的核心算法,第二部分是扩充功能部分,比如Api 中参数Executor的作用、Async的作用、多次Completable的怎么处理。

算法部分

从链接文章中的结构流程示意图中,是不是很明显可以感受到,这里是一个树结构。这里不像二叉树,有左子树、右子树,这里的子节点是没有定量的,由next,将一个节点的兄弟节点链接起来,用src,stack 来连接好父子节点 。
原文章
从源码也可见一般啊,见源码中的注释,用一个push,就可以源源不断的构建子节点。

 private <V> CompletableFuture<V> uniApplyStage(
        Executor e, Function<? super T,? extends V> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<V> d =  new CompletableFuture<V>();
        if (e != null || !d.uniApply(this, f, null)) {
            // 这里就是在构建一个子节点,包含了子Completable d,父Completable this,以及方法f
            UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
            //这里的push,就是要将添加一个子节点
            push(c);
            c.tryFire(SYNC);
        }
        return d;
    }
 // 不断尝试添加  
 final void push(UniCompletion<?,?> c) {
        if (c != null) {
            while (result == null && !tryPushStack(c))
                lazySetNext(c, null); // clear on failure
        }
    }   
//添加的方法
 final boolean tryPushStack(Completion c) {
        //h为当前链表的头节点,也可以说压栈的栈顶。
        Completion h = stack;
        //将当前节点c的next指向h,那么c就是链表的头节点
        lazySetNext(c, h);
        //再将stack的栈顶指向c。
        return UNSAFE.compareAndSwapObject(this, STACK, h, c);
	    }     

既然是树的结构,那么触发(从父节点开始触发),就涉及到树的遍历。单从所有的节点都会从一个线程中运行说起。

这段是核心算法,也是遍历的过程

  • 1.首先抛个引子,二叉树的先序遍历,以迭代的方式,
  • 2.迭代过程是,一个循环不断的从栈顶取节点,取出后又将 此节点的右子节点 左子节点依次压入栈中.
  • 3.再来思考,CompletableFuture的结构,CompletableFuture的stack 指向的是Completion,而Completion 又包含了CompletableFuture.
    • 可以理解为根节点保存了一个栈顶元素C,C中的内容包含了一个子CompletableFuture,同时栈中的Next 也指向根节点的下一个儿子C,即这些C的src都指向根节点,相信上面的图,也是这么表示的.
  • 4.这次遍历算法中的f,可以是根节点也有可能不是根节点,当不是根节点时,就相当于引子中的栈顶节点(已经完成了子节点f的计算),需要f的栈顶元素压入根节点的栈中.其中需要判断 f 是否还有很多栈元素(即C >1),如果C>1,就需要将f的栈元素压入根节点的栈,保留最后一个,最后一个也会成为栈顶元素,就不必再压入栈,直接计算即可.
  • 5.因为Completion中以链表存储很多Completion.所以在Completion压入根节点栈时,是以链表的遍历方式,从头节点开始依次压入根节点栈中.

此算法,设计精妙没有赘余,出自高手啊.

  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.
         */
        
        CompletableFuture<?> f = this; Completion h;
        //如果f的栈顶元素不为null,或者f不是根节点,就要将f的栈元素压入根节点的栈中了.
        while ((h = f.stack) != null ||
               (f != this && (h = (f = this).stack) != null)) {
            CompletableFuture<?> d; Completion t;
            //f的stack 指向stack的next
            if (f.casStack(h, t = h.next)) {
            //next不为null,说明f的Completion>1,考虑f不是根节点时,要压栈
                if (t != null) {
                    //不是根节点
                    if (f != this) {
                        //压栈,这里的this 是根节点,压入根节点的栈中
                        pushStack(h);
                        continue;
                    }
                    h.next = null;    // detach
                }
                //这里就是操作h,燃烧h,获得计算结果,如果h中的Completable中没有栈顶元素就返回null,有则返回h中的Completable。当然,这是没有多线程时的处理逻辑。
                f = (d = h.tryFire(NESTED)) == null ? this : d;
            }
        }
    }
    

好了,核心算法部分就先到这里,接下来,就要介绍,Api参数中Execute的作用,以及Async的用处。

其实多线程的加入,主要体现在遍历的区别,当一个子节点需要在其它线程运行时,那么就从此子节点开始为新的根节点,在其它线程开始遍历。
而源码中的体现在于

//这是completion类的方法,
 final boolean claim() {
            Executor e = executor;
            if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
                if (e == null)
                    return true;
                executor = null; // disable
                //用其它的线程来运行此节点
                e.execute(this);
            }
            return false;
        }
 // 在同样是tryFire的中,因为uiApply()里会调用claim(),所以,根节点在运行此方法时
 // 会返回null,那么根节点就不再遍历此节点.
 // 此节点会在别的线程运行此方法,并且继续遍历此节点的子节点(如果有)
 final CompletableFuture<V> tryFire(int mode) {
            CompletableFuture<V> d; CompletableFuture<T> 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);
        }        

那么Async 呢?从api的角度,主要体现在线程池的选择不同。

 public <U> CompletableFuture<U> thenApply(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }

    public <U> CompletableFuture<U> thenApplyAsync(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(asyncPool, fn);
    }

    public <U> CompletableFuture<U> thenApplyAsync(
        Function<? super T,? extends U> fn, Executor executor) {
        return uniApplyStage(screenExecutor(executor), fn);
    }

接下来再来聊一聊,applyToEither 和 thenCombine 这两个api,共同点是两者都两个Completable组成,来影响结果,区别是一个是or,一个是and。来看源码。
这里只贴核心逻辑,不贴跳转逻辑

//applyToEither的部分核心逻辑,a代表一个CompletableFuture,b代表一个CompletableFuture,
//一个有a或b,任意一个有结果了,就可以往下执行。
final <R,S extends R> boolean orApply(CompletableFuture<R> a,
                                          CompletableFuture<S> b,
                                          Function<? super R, ? extends T> f,
                                          OrApply<R,S,T> c) {
        Object r; Throwable x;
        if (a == null || b == null ||
            ((r = a.result) == null && (r = b.result) == null) || f == null)
            return false;
//thenCombine 的部分核心逻辑,a代表一个CompletableFuture,b代表一个CompletableFuture,
//如果a、b中任意一个没有结果都不往下执行。
final <R,S> boolean biApply(CompletableFuture<R> a,
                                CompletableFuture<S> b,
                                BiFunction<? super R,? super S,? extends T> f,
                                BiApply<R,S,T> c) {
        Object r, s; Throwable x;
        
        if (a == null || (r = a.result) == null ||
            b == null || (s = b.result) == null || f == null)
            return false;

好了,今天就要这了,当然,还有allOf 、anyOf 的api,留给感兴趣的朋友去自己探究。

ps:流程图来自流程图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值