CompletableFuture 详解(二):supplyAsync / runAsync 实现原理 源码分析(转载)

前面CompletableFuture详解(一):基本概念及用法中讲了CompletableFuture的使用,从这篇开始,我会逐步讲解CompletableFuture的实现,以便于大家更深入的理解CompletableFuture是如何运行的,从而有利于大家正确且灵活地使用CompletableFuture。

本篇主要介绍 supplyAsync和runAsync的实现。

1、supplyAsync的实现

我们主要看入参为Supplier的版本。因为另外一个版本的supplyAsync函数,除了多了一个自定义线程池作为入参,其他均相同,所以没必要单独分析。

private static final Executor asyncPool = useCommonPool ?
	ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();



public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
	return asyncSupplyStage(asyncPool, supplier);
}



static <U> CompletableFuture<U> asyncSupplyStage(Executor e,
												 Supplier<U> f) {
	if (f == null) throw new NullPointerException();
	CompletableFuture<U> d = new CompletableFuture<U>();
	e.execute(new AsyncSupply<U>(d, f));
	return d;
}

上面为java 8中该函数的实现源码。可以看到,当supplyAsync入参只有supplier时,会默认使用asyncPool作为线程池(一般情况下为ForkJoinPool的commonPool),并调用内部方法asyncSupplyStage执行具体的逻辑。在asyncSupplyStage方法中,程序会创建一个空的CompletableFuture返回给调用方。同时该CompletableFuture和传入的supplier会被包装在一个AsyncSupply实例对象中,然后一起提交到线程池中进行处理。

值得注意的是,当supplyAsync返回时,调用方只会拿到一个空的CompletableFuture实例。看到这里,我们可以猜测,当计算最终完成时,计算结果会被set到对应的CompletableFuture的result字段中。调用方通过join或者get就能取到该CompletableFuture的result字段的值。所以,虽然实际创建CompletableFuture的线程和进行任务计算的线程不同,但是最终会通过result来进行结果的传递。这种方式与传统的Future中结果传递方式类似(计算线程set值,使用线程get值)。

为了证实我们的猜测,继续看AsyncSupply的源码。

static final class AsyncSupply<T> extends ForkJoinTask<Void>
            implements Runnable, AsynchronousCompletionTask {
        CompletableFuture<T> dep; Supplier<T> fn;
        AsyncSupply(CompletableFuture<T> dep, Supplier<T> 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<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();
            }
        }
    }

AsyncSupply的源码很简单。首先,它实现了Runnable接口,所以被提交到线程池中后,工作线程会执行其run()方法。因此,我们只需要搞清楚其run()中的实现即可。

在run()中,程序首先检查了传入的CompletableFuture和Supplier是否为空,如果均不为空,再检查CompletableFuture的result是否为空,如果不为空,则说明CompletableFuture的值已经被其他线程主动设置过了(这也是CompletableFuture与Future最大的不同之处),因此这里就不会再被重新设置一次。如果result为空,则调用Supplier(源码中的 f 变量)的get()方法,执行具体的计算,然后通过completeValue方法将结果设置到CompletableFuture中。最后,调用CompletableFuture的postComplete()方法,执行连接到当前CompletableFuture上的后置任务。由于postComplete()主要用于处理后置任务,所以这里不展开讲解,在后续讲到任务连接时,会详细讲解。

通过对AsyncSupply中run方法的分析,也基本证实我们之前的猜测。即计算任务由工作线程调用run方法执行,并设置到CompletableFuture的结果中。其他线程中的使用方,则可以调用该CompletableFuture的join或者get方法获取其结果。

2、runAsync的实现

runAsync除了入参是Runnable类型的,其实现原理基本与supplyAsync一致。因此在理解了supplyAsync的基础上,理解runAsync就非常快了。

    public static CompletableFuture<Void> runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }
 
 
    static CompletableFuture<Void> asyncRunStage(Executor e, Runnable f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<Void> d = new CompletableFuture<Void>();
        e.execute(new AsyncRun(d, f));
        return d;
    }

通过上面的源码可以看出,runAsync也会生成一个空的CompletableFuture,并包装在AsyncRun中提交到线程池中执行。这与supplyAsync是完全一致的。

但是,需要注意的是,由于Runnable没有返回值,这里返回的CompletableFuture的结果值是Void类型的。

 static final class AsyncRun extends ForkJoinTask<Void>
            implements Runnable, AsynchronousCompletionTask {
        CompletableFuture<Void> dep; Runnable fn;
        AsyncRun(CompletableFuture<Void> dep, Runnable 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<Void> d; Runnable f;
            if ((d = dep) != null && (f = fn) != null) {
                dep = null; fn = null;
                if (d.result == null) {
                    try {
                        f.run();
                        d.completeNull();
                    } catch (Throwable ex) {
                        d.completeThrowable(ex);
                    }
                }
                d.postComplete();
            }
        }
    }

AsyncRun和AsyncSupply的实现略有不同,AsyncRun的run中,计算的执行是通过调用传入的Runnable(源码中的 f 变量)的run方法进行的。由于没有返回值,所以这里在设置CompletableFuture的值时,使用其completeNull()方法,设置一个特殊的空值标记。

通过上述分析,我们基本了解了asyncSupply和asyncRun的实现原理。在后续的文章中,我会主要介绍任务连接函数及其原理。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值