前面CompletableFuture详解(一):基本概念及用法中讲了CompletableFuture的使用,从这篇开始,我会逐步讲解CompletableFuture的实现,以便于大家更深入的理解CompletableFuture是如何运行的,从而有利于大家正确且灵活地使用CompletableFuture。
本篇主要介绍 supplyAsync和runAsync的实现。
我们主要看入参为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方法获取其结果。
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的实现原理。在后续的文章中,我会主要介绍任务连接函数及其原理。