【51CTO.com原创稿件】
一、引言
一说到异步任务,很多人上来咔咔新建个线程池。为了防止线程数量肆虐,一般还会考虑使用单例模式创建线程池,具体使用方法大都如下面的代码所示:
@Test
publicvoiddemo1() throwsExecutionException, InterruptedException {
ExecutorServiceexecutorService=Executors.newFixedThreadPool(5);
Futurefuture1=executorService.submit(newCallable() {
@Override
publicObjectcall() throwsException {
returnThread.currentThread().getName();}
});
System.out.println(future1.get());
executorService.execute(newRunnable() {
@Overridepublicvoidrun() {
System.out.println(Thread.currentThread().getName());
}
});
}
经常使用 JavaScript 的同学相信对于异步回调的用法相当熟悉了,毕竟 JavaScript 拥有“回调地狱”的美誉。
我们大 Java 又开启了新一轮模仿之旅。
java.util.concurrent 包新增了 CompletableFuture 类可以实现类似 JavaScript 的连续回调。
二、两种基本用法
先来看下 CompletableFuture 的两种基本⽤法,代码如下:
@Test
publicvoid index1() throws ExecutionException, InterruptedException {
CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName());
CompletableFuture completableFuture2 = CompletableFuture.runAsync(() -> Thread.currentThread().getName());
System.out.println(completableFuture1.get()); System.out.println(completableFuture2.get());
}
打印输出:
ForkJoinPool.commonPool-worker-1
null
初看代码,第一反应是代码简洁。直接调用 CompletableFuture 类的静态方法,提交任务方法就完事了。但是,随之而来的疑问就是,异步任务执行的背后是一套什么逻辑呢?是一对一使用newThread()还是依赖线程池去执行的呢。
三、探索线程池原理
翻阅 CompletableFuture 类的源码,我们找到答案。关键代码如下:
private static final booleanuseCommonPool=
(ForkJoinPool.getCommonPoolParallelism() >1);
/**
* Default executor -- ForkJoinPool.commonPool() unless it cannot
* support parallelism.
*/
private static final Executor asyncPool=useCommonPool?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
可以看到 CompletableFuture 类默认使⽤的是 ForkJoinPool.commonPool() ⽅法返回的线程池。当 然啦,前提是 ForkJoinPool 线程池的数量⼤于 1 。否则,则使⽤ CompletableFuture 类⾃定义的 ThreadPerTaskExecutor 线程池。 ThreadPerTaskExecutor 线程池的实现逻辑⾮常简单,⼀⾏代码简单实现了 Executor 接⼝,内部执⾏ 逻辑是⼀条任务对应⼀条线程。代码如下:
/** Fallback if ForkJoinPool.commonPool() cannot support parallelism */
staticfinal class ThreadPerTaskExecutor implements Executor {
publicvoidexecute(Runnable r) { new Thread(r).start(); }
}
四、两种异步接⼝
之前我们使⽤线程池执⾏异步任务时,当不需要任务执⾏完毕后返回结果的,我们都是实现 Runnable 接⼝。⽽当需要实现返回值时,我们使⽤的则是 Callable 接⼝。 同理,使⽤ CompletableFuture 类的静态⽅法执⾏异步任务时,不需要返回结果的也是实现 Runnable 接⼝。⽽当需要实现返回值时,我们使⽤的则是 Supplier 接⼝。其实,Callable 接⼝和 Supplier 接⼝ 并没有什么区别。 接下来,我们来分析⼀下 CompletableFuture 是如何实现异步任务执⾏的。
runAsync
CompletableFuture 执⾏⽆返回值任务的是 runAsync() ⽅法。该⽅法的关键执⾏代码如下:
staticCompletableFuture asyncRunStage(Executor e, Runnable f) {
if (f == null) throw new NullPointerException();
CompletableFuture d = new CompletableFuture();
e.execute(new AsyncRun(d, f));
returnd;
}
可以看到,该⽅法将 Runnable 实例作为参数封装⾄ AsyncRun 类。实际上, AsyncRun 类是对 Runnable 接⼝的进⼀步封装。实际上,AsyncRun 类也是实现了 Runnable 接⼝。观察下⽅ AsyncRun 类的源码,可以看到 AsyncRun 类的 run() ⽅法中调⽤了 Runnable 参数的 run() ⽅法。
publicvoid 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();
}
}
当提交的任务执⾏完毕后,即 f.run() ⽅法执⾏完毕。调⽤ d.completeNull() ⽅法设置任务执⾏结 果为空。代码如下:
/** The encodingofthenullvalue. */
staticfinal AltResult NIL = new AltResult(null);
/** Completes withthenullvalue, unless already completed. */
final boolean completeNull() {
returnUNSAFE.compareAndSwapObject(this, RESULT,n