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  CompletableFuture supplyAsync(Supplier supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }



    static  CompletableFuture asyncSupplyStage(Executor e,
                                                     Supplier f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture d = new CompletableFuture();
        e.execute(new AsyncSupply(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 extends ForkJoinTask
            implements Runnable, AsynchronousCompletionTask {
        CompletableFuture dep; Supplier fn;
        AsyncSupply(CompletableFuture dep, Supplier 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 d; Supplier 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 runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }


    static CompletableFuture asyncRunStage(Executor e, Runnable f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture d = new CompletableFuture();
        e.execute(new AsyncRun(d, f));
        return d;
    }

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

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

    static final class AsyncRun extends ForkJoinTask
            implements Runnable, AsynchronousCompletionTask {
        CompletableFuture dep; Runnable fn;
        AsyncRun(CompletableFuture 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 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的实现原理。在后续的文章中,我会主要介绍任务连接函数及其原理。

你可能感兴趣的:(java,java,多线程)