CompletableFuture
api
原理
CompletableFuture中包含两个字段:result和stack。result用于存储当前CF的结果,stack(Completion)表示当前CF完成后需要触发的依赖动作(Dependency Actions),去触发依赖它的CF的计算,依赖动作可以有多个(表示有多个依赖它的CF),以栈的形式存储,stack表示栈顶元素。
这种方式类似“观察者模式”,依赖动作(Dependency Action)都封装在一个单独Completion子类中。下面是Completion类关系结构图。CompletableFuture中的每个方法都对应了图中的一个Completion的子类,Completion本身是观察者的基类。
- UniCompletion继承了Completion,是一元依赖的基类,例如thenApply的实现类UniApply就继承自UniCompletion。
- BiCompletion继承了UniCompletion,是二元依赖的基类,同时也是多元依赖的基类。例如thenCombine的实现类BiRelay就继承自BiCompletion。
各个类含有的私有属性:
Completion:
Completion next;(链表)
UniCompletion<T,V>:
Executor executor; // executor to use (null if none)
CompletableFuture<V> dep; // the dependent to complete
CompletableFuture<T> src;
BiCompletion<T,U,V>:
CompletableFuture<U> snd; // second source for action
- 每个CompletableFuture都可以被看作一个被观察者,其内部有一个Completion类型的链表成员变量stack,用来存储注册到其中的所有观察者。当被观察者执行完成后会弹栈stack属性,依次通知注册到其中的观察者。上面例子中步骤fn2就是作为观察者被封装在UniApply中。
- 被观察者CF中的result属性,用来存储返回结果数据。这里可能是一次RPC调用的返回值,也可能是任意对象,在上面的例子中对应步骤fn1的执行结果。
部分源码分析
join逻辑
public T join() {
Object r;
return reportJoin((r = result) == null ? waitingGet(false) : r);
}
任务执行完了(result != null),直接返回,否则调用waitingGet(有阻塞当前线程的逻辑)
阻塞调用链
new Signaller对象 -> 放入当前CompletableFuture的stack的头节点 -> 阻塞当前线程
阻塞【LockSupport.park(在Signaller的block方法中调用)】
唤醒【LockSupport.unpark(在Signaller的tryFire方法中调用)】
示例
System.out.println(new Date());
CompletableFuture<String> one = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("第一个xx " + new Date() +" " + Thread.currentThread().getName());
return "第一个异步任务";
});
CompletableFuture<String> two = one.thenApply((res) -> {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("第2个xx " + new Date() +" " + Thread.currentThread().getName());
return "第2个异步任务";
});
CompletableFuture<String> three = one.thenApply((res) -> {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("第3个xx " + new Date() +" " + Thread.currentThread().getName());
return "第3个异步任务";
});
System.out.println(" one.join:" + one.join() + " " + new Date());
System.out.println(" two.join:" + two.join() + " " + new Date());
System.out.println(" three.join:" + three.join() + " " + new Date());
System.out.println("end");
执行结果
Mon Jul 18 21:15:55 CST 2022
第一个xx Mon Jul 18 21:16:10 CST 2022 pool-1-thread-1【one 21:16:10执行完 pool-1-thread-1线程】
第2个xx Mon Jul 18 21:16:30 CST 2022 main【two 21:16:30执行完 main线程】
one.join:第一个异步任务 Mon Jul 18 21:16:30 CST 2022【主线程的join在21:16:30返回结果】
two.join:第2个异步任务 Mon Jul 18 21:16:30 CST 2022【主线程的join在21:16:30返回结果】
第3个xx Mon Jul 18 21:16:40 CST 2022 pool-1-thread-1【three 21:16:40执行完 pool-1-thread-1线程】
three.join:第3个异步任务 Mon Jul 18 21:16:40 CST 2022【主线程的join在21:16:40返回结果】
end
可以看到one.join返回结果的时间和one任务执行完的时间是不一致的,具体原因如下
分析
执行完
CompletableFuture<String> one = CompletableFuture.supplyAsync(fn1); CompletableFuture<String> two = one.thenApply(fn2); CompletableFuture<String> three = one.thenApply(fn3);但还没执行到join方法时,此时的one里的属性如下:
| 主线程 | one线程 |
|---|---|
| 任务编排,此时one的stack依次有three、two | |
| 执行one | |
| CompletableFuture d = one,此时的d、one的stack只有three、two【AsyncSupply的run方法】 | |
| 执行ing…… | |
| one.join | |
| 阻塞(WAIT)…(此时的one的stack有signaller、three、two) | |
| one执行完成 | |
| 调用d.postComplete,依次弹栈(此时的d、one的stack有signaller、three、two,此时会唤醒主线程) | |
| RUNNING… | |
| 继续往下执行,执行postComplete,弹出this的stack【这里是没锁的,不会存在执行CompletableFuture<?> f = this时,两个线程的this都是一样的,导致重复消费吗】 | 继续执行postComplete |
| 此时主线程消费stack里的一个任务 | one线程消费stack的一个任务 |
| 继续消费… | 继续消费… |
| stack为空,返回结果给主线程 | stack为空,结束 |
可以看到join是等到清空stack后才返回结果给主线程的,所以join的时间和任务执行时间不一致
备注:不会导致重复消费,多线程执行postComplete,在【this】这里只会让一个线程执行,其他线程重新循环
final void postComplete() {
/*
* On each step, variable f holds current dependents to pop
* and run. It is extended along only one path at a time,
* pushing others to avoid unbounded recursion.
*/
CompletableFuture<?> f = this; Completion h;
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
CompletableFuture<?> d; Completion t;
if (f.casStack(h, t = h.next)) {【this】
if (t != null) {
if (f != this) {
pushStack(h);
continue;
}
h.next = null; // detach
}
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}
当前任务是由哪个线程执行的
1、有多线程在同时执行postComplete方法
例如上面的例子,任务的执行线程是这几个线程的其中一个
2、没有多线程在同时执行postComplete方法
1)、同步方法(即不带Async后缀的方法,如thenApply、thenAccept),有两种情况
- 如果注册时被依赖的操作已经执行完成,则由当前线程执行
- 如果注册时被依赖的操纵还没执行完,则由回调线程执行
CompletableFuture<String> one = CompletableFuture.supplyAsync(fn1, threadpool);
CompletableFuture<String> two = one.thenApply(fn2);
CompletableFuture<String> three = one.thenApply(fn3);
此时one、two、three任务是由threadpool线程执行的,且执行顺序是one -> three -> two
2)、异步方法(即带Async后缀的方法,如thenApplyAsync、thenAcceptAsync)
此时one、two、three由threadpool线程池分配线程执行
CompletableFuture<String> one = CompletableFuture.supplyAsync(fn1, threadpool);
CompletableFuture<String> two = one.thenApplyAsync(fn2, threadpool);
CompletableFuture<String> three = one.thenApplyAsync(fn3, threadpool);
结果
Mon Jul 18 21:41:04 CST 2022
第一个xx Mon Jul 18 21:41:19 CST 2022 pool-1-thread-1
one.join:第一个异步任务 Mon Jul 18 21:41:19 CST 2022
第2个xx Mon Jul 18 21:41:39 CST 2022 pool-1-thread-2
two.join:第2个异步任务 Mon Jul 18 21:41:39 CST 2022
第3个xx Mon Jul 18 21:41:49 CST 2022 pool-1-thread-3
three.join:第3个异步任务 Mon Jul 18 21:41:49 CST 2022
end
stack的存取逻辑
后续补充