背景
Java的异步并发Future
接口表示异步计算的结果。CompletableFuture
是对Future
接口的增强,它实现CompletionStage
接口,允许链式组合异步操作,组合多个异步任务的结果,处理异常情况,任务结束时执行回调方法。
CompletableFuture定义与用法
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
volatile Object result; // Either the result or boxed AltResult
volatile Completion stack;
}
CompletableFuture
类有2个成员变量,volatile
修饰符保证多线程环境下变量可见性。result
表示当前任务的结果,可能是正常结果,可能是异常对象。stack
是当前任务确定结果后接下来所有执行的任务栈。
CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
常用的创建方法中,supplyAsync
有返回值,runAsync
没有返回值(返回值类型为Void
)。async
表示异步,即runnable
任务不由调用线程执行,而是由线程池执行。
默认线程池是ForkJoinPool
,这是一个全局唯一的线程池。为了管理任务,建议对不同类型任务分别分配线程池。
Completion
abstract static class Completion extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
volatile Completion next;
abstract CompletableFuture<?> tryFire(int mode);
abstract boolean isLive();
public final void run() {
tryFire(ASYNC); }
public final boolean exec() {
tryFire(ASYNC); return false; }
public final Void getRawResult() {
return null; }
public final void setRawResult(Void v) {
}
}
Completion
是CompletableFuture
的内部类。CompletableFuture
强调存储任务结果,而Completion
强调计算单个任务
以及组合多个任务
,强调动作,所以它继承Runnable
,可以异步执行。例子:
ExecutorService executor = new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
CompletableFuture base = new CompletableFuture();
base.thenApply(u -> {
return "h";
});
base.thenAcceptAsync(s -> System.out.println(s), executor);
base.thenRunAsync(() -> System.out.println("c"), executor);
// 时间点1
base.complete("s");
程序运行到时间点1,base
任务还没完成(等base.complete("s")
结束base
对象的result
=s
才算完成),它将三个thenXxx()
方法装在栈里,以便将来调用,关系如图。每个thenXxx()
方法都对应一个Completition
对象,后调用的方法在栈顶,先调用的在栈底。栈的底层是链表。stack
表示当前任务完成后将要执行的任务,即栈顶元素。
注意:这里的栈是Treiber stacks
,用CAS原子性更改栈顶元素,实现线程安全地更改栈。
源码中thenApply()
方法对应的不是Completion
类本身,而是它的子类UniApply
类。Completion
的子类很多,如下图,它们的区别是
- 输入不同。比如
Uni
开头的子类表示1个输入,Bi
开头的子类表示2个输入。比如UniApply
接收Function
类型任务,UniRun
接受Runnable
类型任务。 - 功能不同。比如
BiRun
表示两个输入都已运算结束才行,OrRun
表示其中1个结束就行。比如Signaller
类用于当前线程挂起后(比如get()
方法)将来唤醒本线程(就是执行LockSupport.unpark(本线程对象)
)。
图片来自https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html。
thenApplyAsync方法
以CompletableFuture#thenApplyAsync()
为例,分析执行流程。如果前置任务已经有结果,那么不加入stack
栈,直接运算,如果前置任务还没算完,那么用unipush()
方法加入栈。
public <