CompletableFuture

目录

CompletableFuture与Future有什么不同
开发背景
对比举例
Future实现
CompletableFuture实现
不同点
相同点
CompletableFuture的基本使用
创建一个异步任务
获取任务结果
任务完成后的回调
thenApplyXXX
thenAcceptXXXX
thenRunXXX
whenCompleteXXX
handleXXX
thenComposeXXX
依赖于两个任务的回调
前置两个任务都完成后
thenCombineXXX
thenAcceptBothXXX
runAfterBothXXX
前置任意一个任务完成后
applyToEitherXXX
acceptEitherXXX
runAfterEitherXXX
举例
依赖于多个任务的回调
任意一个完成后–anyOf
所有任务都完成后–allOf
举例
异常处理
API的分类与总结
三种基本任务类型
三种重载方法
分类
源码解析
如何创建一个任务
thenApply是如何执行的
uniApplyStage
封装函数是如何被执行的
函数式接口在哪里被调用
UniApply源码解析

后续任务是如何被调用的
调用者触发执行
前置任务触发
postFire方法

后续任务的后续任务是如何被执行的
postComplete源码分析
stack整理流程图
allOf、anyOf是如何实现的

CompletableFuture与Future有什么不同

开发背景
在使用多线程处理任务时,经常会需要等待某一阶段的任务执行完成之后,根据阶段结果再开启新的异
步任务。
这个逻辑可以使用Future来实现,通过Future的isDone方法或get方法来判断异步任务是否完成或获取
结果。这种方法的问题在于,isDone轮询会消耗CPU资源,并且不能够及时的获取的任务完成的状态;
get方法会使调用的线程阻塞,无法处理后续逻辑。
CompletableFuture提供了回调机制和丰富的Stream API,可以很好的完成异步任务的回调和后续任务
处理。并且CompletableFuture可以很好的描述各个异步任务之间的关系,让逻辑更清晰直观。内部的
无锁并发栈也能够高性能的完成异步任务之间的并行调用。
对比举例
模拟场景:
小白去餐厅吃饭,点了一份西红柿鸡蛋和一碗米饭。点完之后就玩王者等吃饭。
此时服务员开始做米饭,厨师开始炒菜。做好之后由服务员打饭上菜,叫小白吃饭。
Future实现
@Test
public void FutureExample2() throws ExecutionException, InterruptedException
{
ExecutorService waiters = Executors.newFixedThreadPool( 2 );
ExecutorService cookers = Executors.newFixedThreadPool( 2 );
printTimeAndThread("小白进入餐厅");
printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
Future<String> makeRice = waiters.submit(() -> {
ThreadTools.sleepSeconds( 2 );
printTimeAndThread("饭好了");
return "米饭";
});
Future<String> cookDish = cookers.submit(() -> {
ThreadTools.sleepSeconds( 2 );
printTimeAndThread("菜好了");
return "番茄炒蛋";
});
Future<String> getRice = waiters.submit(() -> {
final long start = System.currentTimeMillis();
String rice = makeRice.get();
String dish = cookDish.get();
ThreadTools. sleepSeconds( 2 );
printTimeAndThread(String.format("服务员打饭完成,耗时%d",
System.currentTimeMillis() - start));
return String.format("%s + %s 好了", dish, rice);
});
printTimeAndThread("小白在打王者");
printTimeAndThread(String.format("%s ,小白开吃", getRice.get()));
}
结果
CompletableFuture实现
结果
1634092703854 | main | 小白进入餐厅
1634092703854 | main | 小白点了 番茄炒蛋 + 一碗米饭
1634092703856 | main | 小白在打王者
1634092705874 | pool-2-thread-1 | 菜好了
1634092705874 | pool-1-thread-1 | 饭好了
1634092707884 | pool-1-thread-2 | 服务员打饭完成,耗时 4028
1634092707885 | main | 番茄炒蛋 + 米饭 好了 ,小白开吃
@Test
public void CPExample2() {
printTimeAndThread("小白进入餐厅");
printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
CompletableFuture<String> makeRice = CompletableFuture.supplyAsync(() ->
{
sleepSeconds( 2 );
FutureTaskTest.printTimeAndThread("做米饭");
return "米饭";
});
CompletableFuture<String> makeDish = CompletableFuture.supplyAsync(() ->
{
sleepSeconds( 2 );
printTimeAndThread("厨师炒菜");
return "番茄炒蛋";
});
final CompletableFuture<String> allDone = makeDish.thenCombine(makeRice,
(dish, rice) -> {
final long start = System.currentTimeMillis();
sleepSeconds( 2 );
printTimeAndThread(String.format("服务员打饭完成,耗时%d",
System.currentTimeMillis() - start));
return String.format("%s + %s 好了", dish, rice);
});
printTimeAndThread("小白在打王者");
printTimeAndThread(String.format("%s ,小白开吃", allDone.join()));
}
1634822712205 | main | 小白进入餐厅
1634822712205 | main | 小白点了 番茄炒蛋 + 一碗米饭
1634822712207 | main | 小白在打王者
1634822714221 | ForkJoinPool.commonPool-worker-2 | 厨师炒菜
1634822714221 | ForkJoinPool.commonPool-worker-9 | 做米饭
1634822716229 | ForkJoinPool.commonPool-worker-9 | 服务员打饭完成,耗时 2008
1634822716229 | main | 番茄炒蛋 + 米饭 好了 ,小白开吃
不同点
1. CompletableFuture是在前两个任务完成后,自动调用后续任务的;而Future则阻塞了一个线程,
用来等待前两个任务完成。
2. CompletableFuture获取结果的join方法抛出的是RuntimeException,而get方法抛出的是受检异
常InterruptedException, ExecutionException
3. Future需要显示的指定线程池,而CompletableFuture可以默认使用ForkJoinPool。
4. CompletableFuture可以使用类似Stream表达式的API表现出任务之间的逻辑关系,而Future不能
在语法上直观的表示出这个关系。
相同点
对于逻辑上需要获取异步线程计算结果的任务,二者都可能在获取结果时被阻塞。

CompletableFuture的基本使用

CompletableFuture提供的api,大概可以描述一下几种场景:
1. 创建一个异步任务
2. 获取任务的结果
3. 在一个异步任务完成后执行某个逻辑
4. 在两个任务都完成后执行某个逻辑
5. 在两个任务任意一个完成后执行某个逻辑
6. 任意多个任务都完成后执行某个逻辑
7. 任意多个任务,其中一个完成后执行某个逻辑

创建一个异步任务

CompletableFuture提供了两个API用于开启一个异步任务。分别是使用Runnable接口对象作为参数的
runAsync方法与使用Supplier接口对象对作为参数的supplyAsync方法。两种API的区别就是Runnable
接口与Supplier接口的区别,即runAsync方法没有返回值,而supplyAsync方法有返回值。
两个方法使用默认的ForkJoinPool作为线程池执行异步任务。如果需要指定其他的线程池,可以使用以
下重载API:
使用举例:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor
executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor
executor)
执行结果:

获取任务结果

与Future的区别
CompletableFuture比Future多提供了两种获取结果的方法:getNow与Join。
1. getNow类似于Java8的Stream中的optional.orElse()方法,当结果不存在或没有完成时返回给定的
结果。
2. join与get类似,都会阻塞线程,二者区别在于抛出的异常不同。join可能抛出unchecked
exception,不需要强制try catch。get在方法签名中声明了受检异常,必须要在编码时进行处理。
使用举例
@Test
public void CompletableFutureStart() {
CompletableFuture.runAsync(() -> {
ThreadTools.sleepSeconds( 1 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
});
CompletableFuture.supplyAsync(() -> {
ThreadTools.sleepSeconds( 2 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
return "success";
});
ThreadTools.sleepSeconds( 5 );
}
run on ForkJoinPool.commonPool-worker- 1
run on ForkJoinPool.commonPool-worker- 2
public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
public T join()
@Test
public void CompletableFutureGet() {
try {
final Void unused = CompletableFuture.runAsync(() -> {
ThreadTools.sleepSeconds( 1 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
}).get();
System.out.println("CompletableFuture task 1 result "+unused);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
final String result = CompletableFuture.supplyAsync(() -> {
ThreadTools.sleepSeconds( 2 );
System.out.printf("run on %s %n", Thread.currentThread().getName());
return "success";
}).join();
System.out.println("CompletableFuture task2 result "+result);
ThreadTools.sleepSeconds( 5 );
结果

任务完成后的回调

CompletableFuture的一大特性就是丰富的基于回调的API。当某一个阶段的任务完成后,有时希望能够
及时的获取结果,并操作下一步的逻辑。如果使用Future来实现,那么可能需要阻塞的通过get获取结
果,或不断的通过isDone方法轮询,确认任务是否完成。但使用CompletableFuture,这一切就可以很
自然并高效的完成。并且CompletableFuture的回调对象可以是多个,多个回调对象会被依次回调执
行。
针对与某“一个”任务完成的回调,有多个API可以调用。这些API的区别基本在于入参与返回值,但执行
的时机都是当前阶段的任务完成后。具体如下:
thenApplyXXX
作用:当前阶段(调用该方法的CompletionStage对象)非异常完成时,以 当前阶段的结果为入参 ,执
行给定的函数,函数有返回值。
举例
thenAcceptXXXX
作用:与thenApply类似。当前阶段非异常完成时, 以当前结果为入参 ,执行给定的consumer函数, 函
数没有返回值 。方法返回CompletionStage对象,对象的阶段结果为null。
}
run on ForkJoinPool.commonPool-worker-
CompletableFuture task 1 result null
run on ForkJoinPool.commonPool-worker-
CompletableFuture task2 result success
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U>
fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U>
fn,Executor executor);
@Test
public void RunTest() {
final CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -
> "from supply").thenApply(r -> {
System.out.println(r);
return "success";
});
System.out.println(future1.join());
}
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T>
action,Executor executor);
thenRunXXX
作用:当前阶段非异常完成时,执行制定动作。 没有入参和返回值 。
whenCompleteXXX
作用:当前阶段完成时给定的BiConsumer函数被调用, 入参为抛出的异常(正常结束时为null)和结
果(异常结束时为null) , 函数无返回值 ,方法返回的CompletionStage对象,含的结果或异常与当前
阶段相同(不改变上一阶段的结果)。
特点:可同时处理正常与异常的情况。
handleXXX
作用:当前阶段完成时给定的BiFunction函数被调用, 入参为抛出的异常(正常结束时为null)和结果
(异常结束时为null) , 函数有返回值 。返回的CompletionStage对象为函数式接口的结果包装。
thenComposeXXX
作用:当前阶段非异常完成时,执行给定的函数。函数使用当前阶段的结果作为入参, 函数 返回另一个
CompletionStage对象。thenCompose方法会将这个对象展开,避免结果的嵌套。执行效果类似于
Stream中的flatMap。
举例:
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
public CompletionStage<T> whenComplete(BiConsumer<? super T,? super Throwable>
action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T,? super
Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T,? super
Throwable> action,Executor executor);
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable,? extends
U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable,?
extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable,?
extends U> fn,Executor executor);
public <U> CompletionStage<U> thenCompose(Function<? super T,? extends
CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T,? extends
CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T,? extends
CompletionStage<U>> fn,Executor executor);
/**
* thenCompose举例
*/
@Test
thenCompose结果输出
thenApply结果输出

依赖于两个任务的回调

有时后续的任务可能需要依赖于前两个任务的结果,针对这种情况,CompletableFuture也提供了对应
的API。分为两类,分别是前两个任务都完成,前两个任务任意一个完成后,调用后续任务执行逻辑。具
体如下:
public void FutureTaskTestCompose() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->
"task 1");
final CompletableFuture<String> future2 =
CompletableFuture.supplyAsync(() -> "next stage");
CompletableFuture<String> nextFuture = future1.thenCompose((result) -> {
System.out.printf("future1 result is \"%s\"%n", result);
return future2;
});
System.out.println(future2 == nextFuture);
final String join = nextFuture.join();
System.out.printf("nextFuture result is \"%s\"%n", join);
}
/**
* thenCompose对比thenApply
*/
@Test
public void FutureTaskTestCompose2() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->
"task 1");
final CompletableFuture<String> future2 =
CompletableFuture.supplyAsync(() -> "next stage");
//thenApply返回一个CompletableFuture对象
final CompletableFuture<CompletableFuture<String>> nextFuture =
future1.thenApply((result) -> {
System.out.printf("future1 result is \"%s\"%n", result);
return future2;
});
final CompletableFuture<String> innerStage = nextFuture.join();//获取到的
结果为CompletableFuture对象
final String result = innerStage.join();//获取返回的CompletableFuture对象执
行的结果
System.out.printf("nextFuture result is \"%s\"%n", result);
}
future1 result is "task 1"
false
nextFuture result is "next stage"
future1 result is "task 1"
nextFuture result is "next stage"

前置两个任务都完成后

thenCombineXXX
当调用该方法的前置任务与给点的前置任务都完成时,执行BiFunction函数。 函数的入参为前置两阶段
的返回结果,函数无返回值。 阶段的结果CompletableFuture对象中result字段值为函数计算结果。
thenAcceptBothXXX
作用:与thenAccep类似,但依赖于两个阶段。当前阶段与给定的阶段都非异常的执行完成时, 以两个
阶段的结果为入参 ,执行给定的函数, 函数无返回值 。阶段的结果CompletableFuture对象中result字
段值为null。
runAfterBothXXX
作用:与thenRun类似,当前阶段与给点的阶段都异常的完成时,执行给点的函数。 函数无入参,无返
回值 。阶段的结果CompletableFuture对象中result字段值为null。

前置任意一个任务完成后

applyToEitherXXX
作用:当前阶段或给点的阶段任意一个非异常的完成时,执行给定的函数。 以完成阶段的结果作为函数
的入参,函数有返回值。 阶段的结果CompletableFuture对象中result字段值为函数计算结果。
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U>
other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync (CompletionStage<? extends U>
other, BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync (CompletionStage<? extends U>
other, BiFunction<? super T,? super U,? extends V> fn,Executor executor);
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U>
other,BiConsumer<? super T,? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends
U> other,BiConsumer<? super T,? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends
U> other,BiConsumer<? super T,? super U> action, Executor executor);
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable
action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable
action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable
action,Executor executor);
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T>
other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T>
other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T>
other,Function<? super T, U> fn,Executor xecutor);
acceptEitherXXX
作用:同applyToEither。但函数为consumer函数式接口, 无返回值 。
runAfterEitherXXX
作用:同applyToEither。但函数为Runnable接口, 无入参,无返回值。

举例

输出结果
public CompletionStage<Void> acceptEither(CompletionStage<? extends T>
other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T>
other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T>
other,Consumer<? super T> action,Executor executor);
public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable
action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?>
other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?>
other,Runnable action,Executor executor);
/**
* 依赖于两个前置任务的API举例
*/
@Test
public void bothOrAny() {
final CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() ->
{
ThreadTools.sleepSeconds( 1 );
return "task 1";
});
final CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() ->
{
ThreadTools.sleepSeconds( 2 );
return "task 2";
});
//依赖于两个前置任务都完成
final CompletableFuture<Void> both = task2.thenAcceptBothAsync(task1, (r1,
r2) -> {
System.out.printf("两个任务都已经完成。结果 1 :%s ,结果2: %s%n", r1, r2);
});
//依赖于两个前置任务,但有一个完成即可
final CompletableFuture<Void> any = task1.acceptEitherAsync(task2, (r) -> {
System.out.printf("有一个任务已经完成,结果是:%s%n", r);
});
//阻塞主线程结束
any.runAfterBoth(both, ()->{}).join();
}

依赖于多个任务的回调

除了依赖于一个、两个前置任务的API,更常用的是基于批量任务完成时机的判断。CompletableFuture
也提供了对应的api。

任意一个完成后–anyOf

CompletableFuture提供了静态方法,用来表示当所有的前置任务中,有任意一个任务完成(包括异
常)的阶段。可以在此阶段后执行CompletableFuture的其他操作。

所有任务都完成后–allOf

与anyOf类似,allOf静态方法表示传入的所有前置任务都完成的阶段。可以在此阶段后执行
CompletableFuture的其他操作。
注: 如果异步任务执行异常,也看作是完成状态。

举例

输出结果
有一个任务已经完成,结果是:task 1
两个任务都已经完成。结果 1 :task 2 ,结果2: task 1
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
@Test
public void anyANdAll() {
final ExecutorService threadPool = Executors.newFixedThreadPool( 10 );
final long startAll = System.currentTimeMillis();
final CompletableFuture[] completableFutures = IntStream.rangeClosed( 1 ,
10 ).mapToObj(index -> CompletableFuture.supplyAsync(() -> {
final long start = System.currentTimeMillis();
try {
Thread.sleep( 100 * index);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("线程'%s'执行完成,花费%d毫秒%n",
Thread.currentThread().getName(), System.currentTimeMillis() - start);
return index;
}, threadPool)).toArray(CompletableFuture[]::new);
final Object result = CompletableFuture.anyOf(completableFutures).join();
System.out.printf("any of 得到结果:%s,共花费%d毫秒%n", result,
System.currentTimeMillis() - startAll);
CompletableFuture.allOf(completableFutures).join();//阻塞主线程
System.out.printf("all of 得到结果,共花费%d毫秒%n", System.currentTimeMillis()
  • startAll);
    }

异常处理

处理异常的API与其他回调API类似,但触发时机不同而已。
exceptionally
作用:当前阶段异常时,以异常为入参执行给定的函数。当前阶段非异常执行完成时,不执行逻辑,方
法返回与当前阶段相同的非异常返回值。
举例
输出结果

API的分类与总结

线程'pool-1-thread-1'执行完成,花费 110 毫秒
any of 得到结果: 1 ,共花费 122 毫秒
线程'pool-1-thread-2'执行完成,花费 205 毫秒
线程'pool-1-thread-3'执行完成,花费 300 毫秒
线程'pool-1-thread-4'执行完成,花费 411 毫秒
线程'pool-1-thread-5'执行完成,花费 505 毫秒
线程'pool-1-thread-6'执行完成,花费 615 毫秒
线程'pool-1-thread-7'执行完成,花费 710 毫秒
线程'pool-1-thread-8'执行完成,花费 804 毫秒
线程'pool-1-thread-9'执行完成,花费 915 毫秒
线程'pool-1-thread-10'执行完成,花费 1011 毫秒
all of 得到结果,共花费 1024 毫秒
public CompletionStage<T> exceptionally(Function<Throwable,? extends T> fn);
@Test
public void CPExceptional() {
CompletableFuture.<String>supplyAsync(() -> {
throw new RuntimeException("抛出的异常");
}).exceptionally((e) -> {
System.out.printf("异常消息%s%n", e.getMessage());
return "new result";
}).thenAccept(r -> {
System.out.printf("最终结果1:%s%n", r);
});
CompletableFuture.<String>supplyAsync(() -> "没有抛出异常")
.exceptionally((e) -> {
System.out.printf("异常消息%s%n", e.getMessage());
return "new result";
}).thenAccept(r ->
System.out.printf("最终结果2:%s%n", r));
}
异常消息java.lang.RuntimeException: 抛出的异常
最终结果1:new result
最终结果2:没有抛出异常

三种基本任务类型

只关心前置任务完成的Runable :这种API以Runable函数式接口为参数,Runable接口的特点是没有入
参,没有返回值。因此这是一种只关系前置任务是否完成的API。对应CompletableFuture中的xxxRun
方法。
关心前置任务结果的消费者Consumer :这种API以Consumer函数式接口为参数,Consumer接口有入
参,没有返回值。因此这是一种关心前置任务结果,但只消费前置结果的API。对应CompletableFuture
中的xxxAccept方法。
关心前置任务的生产者Function :这种API以Function函数传接口为参数,Function接口有入参,有返
回值。因此这是一种可以对前置任务的结果进行操作的API。对应的CompletableFuture中的xxxApply
方法。
特殊的任务
创建任务的Supplier接口与Runnable接口

三种重载方法

CompletableFuture描述任务关系的api中,同一个功能的方法大都提供了三个重载方法。类似:xxx,
xxxAsync,xxxAsync(…Executor executor)。三个方法的区别如下:
xxx方法,表示当前任务不会主动提交到线程池中执行。
xxxAsync方法,表示任务需要在默认的Fork/Join线程池中异步执行。
CompletableFuture描述任务关系的api中,同一个功能的方法大都提供了三个重载方法。类似:xxx,
xxxAsync,xxxAsync(…Executor executor)。三个方法的区别如下:
xxx方法,表示当前任务不会主动提交到线程池中执行。
xxxAsync方法,表示任务需要在默认的Fork/Join线程池中异步执行。
xxxAsync(…Executor executor)方法,表示任务需要在指定的线程池中异步执行。

分类

基于CompletableFuture描述的任务关系、三种基本的任务类型以及三种重载方法。API大致可以进行如
下的分类:

源码解析

如何创建一个任务

如何封装函数式接口
以supplyAsync为例,一共两个重载方法。都通过调用asyncSupplyStage方法创建任务。
asyncSupplyStage的源码逻辑很简单,只是创建了一个AsyncSupply对象,然后提交到线程池中等待执
行。AsyncSupply是ForkJoinTask的子类,并且实现了Runnable接口。因此,相当于向线程池中提交了
一个Runnable接口实现对象。
任务如何被执行的
任务被提交到线程池中后,如果使用默认的ForkJoin线程池,则会调用exec方法执行线程池中的任务。
如果是Executor的线程池实现,则会调用run方法执行任务。因此,任务时通过exec或run方法被线程池
触发执行的。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U>
supplier,Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
static <U> CompletableFuture<U> asyncSupplyStage(Executor e,Supplier<U> f) {
if (f == null) throw new NullPointerException();
CompletableFuture<U> d = new CompletableFuture<U>();
e.execute(new AsyncSupply<U>(d, f));//封装Supplier接口并提交到线程池中
return d;
}
static final class AsyncSupply<T> extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
CompletableFuture<T> dep; Supplier<T> fn;
AsyncSupply(CompletableFuture<T> dep, Supplier<T> 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<T> d; Supplier<T> f;
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
if (d.result == null) {
try {
d.completeValue(f.get());//执行supply函数的get方法
} catch (Throwable ex) {
d.completeThrowable(ex);
}

thenApply是如何执行的

调用CompletableFuture对象thenApplyXXX时,最终都在内部调用了uniApplyStage方法,只是参数略
有不同。因此只有理解了uniApplyStage方法,才能理解了thenApply如何工作。

uniApplyStage

执行了uniApplyStage方法后,任务有几种状态。
}
d.postComplete();//调用后续任务
}
}
}
private <V> CompletableFuture<V> uniApplyStage(
Executor e, Function<? super T,? extends V> f) {
if (f == null) throw new NullPointerException();
CompletableFuture<V> d = new CompletableFuture<V>();
if (e != null || !d.uniApply(this, f, null)) {//判断线程池是否为空,为空则直接尝试
执行
//线程池不为空,或前置任务未完成,需要入栈排队。
UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);//新建对象,封装代表执行逻
辑的函数式接口对象f,代表当前阶段的CP对象d,还有前置任务this,以及线程池e;
push(c);//将封装好的任务push到当前CP对象的stack中
c.tryFire(SYNC);//防止push过程中前置任务变更完成状态,漏掉当前阶段的任务。尝试执行
一次。
}
return d;
}
未执行,放到的前置任务的stack中
已经被直接执行完成
提交到了线程池中执行

封装函数是如何被执行的

函数式接口在哪里被调用

UniApply源码解析

后续任务是如何被调用的

CompletableFuture中,有些任务不会立即被执行。而是被放到了前置任务的stack中,那么这些任务是
何时以及如何被触发的呢?

调用者触发执行

final <S> boolean uniApply(CompletableFuture<S> a,
Function<? super S,? extends T> f,
UniApply<S,T> c) {//a前置CP,f当前阶段函数,c封装当前阶
段逻辑的Completion对象
Object r; Throwable x;
if (a == null || (r = a.result) == null || f == null)
return false;//前置任务未完成或其他异常情况
tryComplete: if (result == null) {//当前CP的结果为空
if (r instanceof AltResult) {
if ((x = ((AltResult)r).ex) != null) {
completeThrowable(x, r);//之前任务结果为异常
break tryComplete;
}
r = null;//前置任务结果为空,decode为null
}
try {
if (c != null && !c.claim())//claim判断任务是否被执行过;通过“调用线
程”执行c才是null
return false;
@SuppressWarnings("unchecked") S s = (S) r;
completeValue(f.apply(s));//调用function函数的apply方法,并将结果封装
到CompletableFuture对象中。
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;
}

前置任务触发

前置任务执行完成后,依次执行stack中的任务,任务通过tryFire方法被执行。tryFire方法执行完成后,
调用任务对象的postFire方法,该方法会保证前置阶段与当前阶段的后续任务依次被调用。
UniApply#tyrFire

postFire方法

”a“未完成的情况
private <V> CompletableFuture<V> uniApplyStage(
Executor e, Function<? super T,? extends V> f) {
if (f == null) throw new NullPointerException();
CompletableFuture<V> d = new CompletableFuture<V>();
if (e != null || !d.uniApply(this, f, null)) {
UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
push(c);
c.tryFire(SYNC);//前一个阶段已经完成,当前阶段"自己"触发任务执行
}
return d;
}
final CompletableFuture<V> tryFire(int mode) {
CompletableFuture<V> d; CompletableFuture<T> a;
if ((d = dep) == null ||
!d.uniApply(a = src, fn, mode > 0? null : this))//执行当前阶段任务逻辑
return null;
dep = null; src = null; fn = null;
return d.postFire(a, mode);//清理stack、调用后续任务
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
if (a != null && a.stack != null) {
if (mode < 0 || a.result == null)//嵌套调用时,清理后续无效节点;非嵌套调用
时,当前阶段已经完成,前置阶段未完成(xxxEither),把当前节点从a的stack中清理出去。见下面例
子。
a.cleanStack();//清理stack中的无效节点
else
a.postComplete();//(非嵌套调用,前置任务a已经完成。可能是异步线程执行完毕
的回调)如果当前阶段任务执行完成,前置任务已经完成,前置任务的stack不为空,使用当前线程帮助前置
任务执行stack
}
if (result != null && stack != null) {
if (mode < 0 )
return this;//嵌套调用,返回当前阶段。
else
postComplete();//非嵌套调用,处理当前阶段的stack
}
return null;
}

后续任务的后续任务是如何被执行的

CompletableFuture中的依赖链并不是单向的一条直线。stack栈中的任务,也有着代表着自己的执行阶
段的CompletableFuture对象,这些对象也有自己的stack栈。因此一个CompletableFuture对象的后续
任务调用链是可以由无数多的分支的。
保证这些任务被正确执行的关键在于postComplete方法。
![[CP任务依赖关系.png]]

postComplete源码分析

CompletableFuture<String> future1 = CompletableFuture.
<String>supplyAsync(() -> {
System.out.println("future1执行完成");
return null;
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() ->
{
ThreadTools.sleepSeconds( 1 );
System.out.println("future2 执行完成");
return "future2 result";
});
future2.thenRun(System.out::println);
future2.acceptEitherAsync(future1, (r) -> System.out.println()).join();
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.
* f-->当前CP对象,h-->CP对象的stack,t-->stack的next节点
*/
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)) {//保证postComplete被并发调用时,同一个任务只能
被一个线程拿到
if (t != null) {
if (f != this) {//下一阶段CP对象的stack不为空,将stack压入当前CP对象的
stack中。防止递归调用过深。
pushStack(h);
continue;
}
h.next = null;  // detach
}
f = (d = h.tryFire(NESTED)) == null? this : d;//d-->tryFire的返回值;
d不为空时,f被指向d。即h任务所在阶段的CompletableFuture对象
}
}
}

stack整理流程图

初始状态
执行stack中的第一个任务
执行完成,开始压入stack
stack整理完成

allOf、anyOf是如何实现的

假设有一个任务Z,依赖一组任务[A, B, C, D, E, F, G, H]
对于allOf, 当这组任务都完成时,才会执行Z;对于anyOf, 当这组任务中有任何一个完成,就执行任务
Z。
1. 一种方法是通过isDone方法不断的轮询数组中的对象,直到满足条件
2. 另一种针对allOf的实现,可以依次调用每个任务的get方法,直到所有的任务都完成。
而CompletableFuture提供了更优雅的解决方式,即将原本的数组通过中间对象转变为树结构。因此每
个任务都相当于CompletableFuture中的xxxBoth或xxxEither。
对于allOf, Z只要保证Z1和Z2都完成了就行,Z1和Z2分别保证Z11,Z12 和 Z21,Z22都完成了就像,而
Z11,Z12,Z21,Z22则分别保证了A-H任务都完成。
对应anyOf, Z 只要保证Z1和Z2有一个完成了就像,Z1和Z2联合保证了Z11,Z12,Z21,Z22这 4 个任务只要
有一个完成了就行,同理,Z11,Z12,Z21,Z22则联合保证了A-H中有一个任务完成了就行。
“Z”可以理解为allOf或anyOf返回的CompletableFuture对象,我们可以基于此对象继续使用
CompletableFuture的API编写后续的逻辑。
  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值