java CompletableFuture概述

前言

有天问一朋友为啥喜欢用把逻辑写在前端,他回答:“Promise 用的很爽”。其它后端也有相对应的CompletableFuture。

CompletableFuture 原理

先说Future, 它用来描述一个异步计算的结果。isDone方法可以用来检查计算是否完成,get方法可以用来获取结果,直到完成前一直阻塞当前线程,cancel方法可以取消任务。而对于结果的获取,只能通过阻塞(get())或者轮询的方式[while(!isDone)]. 阻塞的方式违背了异步编程的理念,轮询的方式耗费无谓的CPU资源(CPU空转)。

核心思想

以下是CompletableFuture类的静态方法,也是入口代码:

CompletableFuture.runAsync(()->{});
CompletableFuture.supplyAsync(()->{ return "";});

//其核心原来都是通过代理runnable
public void run() {
      CompletableFuture<T> d; Supplier<T> f;
      if ((d = dep) != null && (f = fn) != null) {
           dep = null; fn = null;
           if (d.result == null) {
                try {
                    //如果正常运行则设值
                    d.completeValue(f.get());
                } catch (Throwable ex) {
                    //如果异常则设置异常
                    d.completeThrowable(ex);
                }
           }
           //通知依赖的任务,我好了
           d.postComplete();
       }
}

依赖的构建

CompletableFuture对象中维护着一个stack,并通过以下方式,向栈中压入任务:

future.whenComplete();
future.thenApply();
future.thenAccept();
future.thenRun();  
   
private CompletableFuture<T> uniWhenCompleteStage(Executor e, BiConsumer<? super T, ? super Throwable> f) {
        if (f == null)
            throw new NullPointerException();
        CompletableFuture<T> d = new CompletableFuture<T>();
        if (e != null || !d.uniWhenComplete(this, f, null)) { 
            //将新的Future包装成Complete
            UniWhenComplete<T> c = new UniWhenComplete<T>(e, d, this, f); 
            //把新的CompletableFuture压入stack中
            push(c);
            // 先调一下钩子方法,检查一下任务是否结束
            c.tryFire(SYNC); 
        }
        return d;
}
  

每个CompletableFuture持有一个Completion栈stack, 每个Completion持有一个CompletableFuture -> dep, 如此递归循环下去,是层次很深的树形结构,所以想办法将其变成链表结构。
在这里插入图片描述

依赖的传递

如果当前任选执行完,全根据stack触发依赖任务

final void postComplete() {
        CompletableFuture<?> f = this; 
        Completion h; 
        //取出stack的Completion放到h中状态消费
        while ((h = f.stack) != null || (f != this && (h = (f = this).stack) != null)) { 
            CompletableFuture<?> d;
            Completion t;
			//cas方式推进stack	
            if (f.casStack(h, t = h.next)) { 
                //排队h已经被其它线程执行过
                if (t != null) {
                    //Completion依赖的CompletableFuture发生变更时
                    if (f != this) {
                        //放回队列
                        pushStack(h);
                        continue; 
                    }
                    h.next = null; 
                }
                //真正的调用执行Completion,当NESTED模式把引Completion转换成到上一级的CompletableFuture,Future依赖变更
                f = (d = h.tryFire(NESTED)) == null ? this : d; 
            }
        }
    }

首先取出头结点,下图中灰色Completion结点,它会返回一个CompletableFuture, 同样也拥有一个stack,策略是遍历这个CompletableFuture的stack的每个结点,依次压入到当前CompletableFuture的stack中,关系如下箭头所示,灰色结点指的是处理过的结点。
在这里插入图片描述
第一个Completion结点返回的CompletableFuture, 将拥有的stack里面的所有结点都压入了当前CompletableFuture的stack里面
在这里插入图片描述

多线程去stack取任务,执行完子任务,如果发现子任务也有依赖任务则把其子任务压入主任务的stack

线程的异步执行

//claim方法只有在tryFire的mode<=0的时候才可以被调用。
 final boolean claim() {
            Executor e = executor;
            //保证只被执行一次
            if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
                //claim方法返回true,表示调用claim方法的线程可以执行fn逻辑处理
                if (e == null) 
                    return true;    
                executor = null; 
                //异步执行
                e.execute(this);
            }
            return false;
}

//postFire是在CompletableFutrue.result完成赋值之后执行的    
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
         // 被依赖的任务存在,且stack不为空,先处理它
        if (a != null && a.stack != null) {
            // 如果是嵌套模式(mode = -1), 或者任务的结果为空,清理下已失效的任务
            if (mode < 0 || a.result == null) 
                a.cleanStack();
            else
                a.postComplete(); 
        }
        // 再处理当前任务
        if (result != null && stack != null) { 
            // 嵌套模式,直接返回自身(树 -> 链表,避免过深的递归调用)
            if (mode < 0)
                return this;
            else
                postComplete(); 
        }
        return null;
    }

claim

  • tryFire在什么时候mode>0?那就是线程池通过run进而调用tryFire的时候。
  • 而mode<=0的时候原线程在调用Completion.postComplete方法做处理,异步处理已经可以执行的fn会提高效率。
    postFire
  • 在执完主任务之后,会帮忙完成父类的任务

任务策略

CompletableFuture还操作了强大 “或” 和 “且” 的功能,函数如下

XXXEitherXXX()
XXXBothXXX()

在这里插入图片描述

  1. BiCompletion是一个有2依赖源的Completion
  2. CoCompletion是一个代理Completion
任务树

根据上面提供的 “或” 和 “且” 的功能,CompletableFuture提供了

allOf()
anyOf()

allOf和anyOf都用到了数组构建成树的策略。假设有一个任务Z(虚拟的,什么都不做),依赖一组任务[A, B, C, D, E, F, G, H]
在这里插入图片描述

如果是奇数:h任务没有 , 为 z22 -> (G, G). z22的左右子节点都为G

  • 对于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中有一个任务完成了就行。

参考

【JUC源码解析】CompletableFuture
CompletableFuture实际操作的源码追究

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值