CompletableFuture 讲解系列
前言
CompletableFuture
估计多数程序员都了解那么一丢丢,
网上介绍 API 的文章一大把,至于原理么,呵呵,不讲。
能把源码讲清楚的文章,约等于 0,甚少我一直没找到。
Doug Lea 的代码,可谓鬼斧神工,精妙绝伦,难懂!
今儿在这儿,我壮胆写篇文章,剖析下源码,探究下原理。
各位路过的大佬儿,还请多多指教。若有谬误,烦请指正。
一、CompletableFuture 是什么?
CompletableFuture
可以简单理解为 Future
的升级版,可以非常方便的进行任务的编排。
以往任务编排,需要借助 CountDownLatch、Semaphore、CyclicBarrier
等来实现,处理逻辑较为复杂。
本文源码层面分析 allOf
和 anyOf
这两个方法。抽丝剥茧,深入剖析 CompletableFuture
的原理。
二、代码示例
1. allOf(CompletableFuture<?>... cfs)
allOf : 可以探测,所有的异步任务,是否全部执行完了。
举个例子,集齐七颗龙珠可以召唤神龙。
就可以分七个支线,每条支线找一颗龙珠。
// 不用CompletableFuture版本
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
long start = System.currentTimeMillis();
int count = 7;
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
int num = i + 1;
pool.submit(() -> {
new Dragon(num).findPreciousness();
latch.countDown();
});
}
String mainThread = Thread.currentThread().getName();
System.out.println(mainThread + "收集龙珠,等待中……");
latch.await();
long end = System.currentTimeMillis() - start;
System.out.println(mainThread + "线程历经" + end + "毫秒,终于集齐七颗龙珠,开始召唤神龙!!");
pool.shutdown();
}
static class Dragon {
private int num;
Dragon(int num) {
this.num = num;
}
private void findPreciousness() {
int cost = RandomUtil.randomInt(1, num + 1) * 1000;
try {
Thread.sleep(cost);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程历经 " + cost + " 毫秒找到第 " + num + " 颗龙珠");
}
}
这个异步版本,用 CountDownLatch
来保证,所有异步任务都执行完。
如果不知道它怎么用,可以看我之前的文章《CountDownLatch源码分析》
集齐七颗龙珠的任务,交给线程池异步处理,
当子任务全部完成时,主线程继续执行,否则阻塞。
再看下,用 CompletableFuture
的 allOf
方法实现的版本
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
long start = System.currentTimeMillis();
int count = 7;
CompletableFuture[] temp = new CompletableFuture[count];
for (int i = 0; i < count; i++) {
int num = i + 1;
temp[num - 1] = CompletableFuture.runAsync(() -> new Dragon(num).findPreciousness(), pool);
}
String mainThread = Thread.currentThread().getName();
System.out.println(mainThread + "收集龙珠,等待中……");
CompletableFuture.allOf(temp).join();
long end = System.currentTimeMillis() - start;
System.out.println(mainThread + "线程历经" + end + "毫秒,终于集齐七颗龙珠,开始召唤神龙!!");
pool.shutdown();
}
示例中 allOf
方法,实现了 CountDownLatch
的功能。
当然,如果你对 stream
写法很熟悉,那代码可以很简单。
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
CompletableFuture[] futures = IntStream.rangeClosed(1, 7)
.mapToObj(num -> new Dragon(num))
.map(dragon -> CompletableFuture.runAsync(dragon::findPreciousness, pool))
.toArray(size -> new CompletableFuture[size]);
CompletableFuture.allOf(futures).join();
pool.shutdown();
}
2. anyOf(CompletableFuture<?>... cfs)
anyOf : 所有的异步任务中,任意一个完成了,就可以被探测到。
举个例子,集齐七颗龙珠可以召唤神龙,这个任务比较难。
找到任意一颗,就摆两桌庆祝下,不管是哪个支线找到的。
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
CompletableFuture[] futures = IntStream.rangeClosed(1, 7)
.mapToObj(num -> new Dragon(num))
.map(dragon -> CompletableFuture.runAsync(dragon::findPreciousness, pool))
.toArray(size -> new CompletableFuture[size]);
CompletableFuture.anyOf(futures).join();
pool.shutdown();
只需要修改一行代码,就可以了。把 allOf
改为 anyOf
即可。
同样用 CountDownLatch
也可以实现,也是改一行
CountDownLatch latch = new CountDownLatch(1);
CountDownLatch 是怎么实现的,不是本文的重点,不展开说了。
有兴趣的话,可以看我之前的文章《CountDownLatch源码分析》。
三、allOf 原理分析
1. 整体流程解释
截图中的1,指主线程提交任务到线程池。
截图中的2,指主线程调用 allOf
方法,生成 一个 CompletableFuture
对象 allOfFuture
。
线程池的工作线程,会执行异步任务,所有异步任务执行完,会给刚刚那个 allOfFuture
对象打个标识。
截图中的3,是主线程会判断那个标识,标识存在,说明所有异步任务已执行完。
若标识不存在,主线程阻塞,等待所有异步任务执行结束时,主线程会被唤醒。
以上是基本的原理,很重要。网上的文章很少会介绍这个。
我是前前后后,看了好多遍才想明白的,我也想找现成的文章看,可是压根就没有。
先有个印象,接着一条一条详细说。
2. runAsync 任务提交
public static CompletableFuture<Void> runAsync(Runnable runnable,
Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
static CompletableFuture<Void> asyncRunStage(Executor e, Runnable f) {
if (f == null) throw new NullPointerExceptio