一、背景
目前一些小伙伴还在使用线程池 + Future的方式,不太了解CompletableFuture,也不习惯于使用更加优雅的CompletableFuture。因此,主要是介绍CompletableFuture, 后续在项目中多多使用CompletableFuture, 提升代码的优雅度,增加开发效率。
二、相关知识
2.1 基本介绍
CompletableFuture是Java 8引入的一个异步编程工具,它实现了Future接口,可以用于表示一个尚未完成的操作。与Java中的Future不同,CompletableFuture提供了更丰富的方法来处理异步任务的结果和异常。CompletableFuture 解决了 Future 的一些问题,同时提供了一些更加强大的功能:
- 多个异步计算结果的组合:Future 之间是相互独立的,无法将多个 Future 组合成一个复杂异步计算。CompletableFuture 提供了一些方法,如 thenApply 、thenCompose 等,可以方便地将多个异步计算组合起来;
- 回调函数:Future 只能通过 Future.get() 方法来阻塞等待异步计算结果,这种方式会影响程序性能。CompletableFuture 可以通过 thenApply 、thenAccept 等方法,在异步计算完成后执行回调函数,提高了程序的响应性能;
- 异常处理:CompletableFuture提供了丰富的异常处理方法,可以在异步任务发生异常时进行捕获和处理;
- 链式调用:CompletableFuture支持链式调用,可以将多个异步操作组合在一起,形成一个异步任务链。这使得代码更加简洁易读。
2.2 各种场景
任务类型 | 解决方案 |
---|---|
简单并行任务 | 线程池 + FutureTask |
聚合任务 | CompletableFuture |
批量并行任务 | CompletionService |
分治任务 | Fork/Join |
三、CompletableFuture的具体使用
3.1 从顶层认识CompletableFuture
从任务关系的角度来认识CompletableFuture:
从阶段维度认识CompletableFuture :
3.2 CompletableFuture提供的具体实现
3.2.1 创建CompletableFuture
提供了四种方法,如下所示:
java
// 使用默认线程池
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
// 执行任务
System.out.println("任务1在默认线程池中执行");
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
// 执行任务
System.out.println("任务2在默认线程池中执行");
return "任务2执行结果";
});
// 指定线程池
Executor executor = Executors.newFixedThreadPool(2);
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
// 执行任务
System.out.println("任务3在指定线程池中执行");
}, executor);
CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> {
// 执行任务
System.out.println("任务4在指定线程池中执行");
return "任务4执行结果";
}, executor);
3.2.2 执行CompletableFuture
从串行角度看:主要是 thenApply、thenAccept、thenRun 和 thenCompose 这四个系列的接口
java
CompletionStage<R> thenApply(fn);
CompletionStage<R> thenApplyAsync(fn);
CompletionStage<Void> thenAccept(consumer);
CompletionStage<Void> thenAcceptAsync(consumer);
CompletionStage<Void> thenRun(action);
CompletionStage<Void> thenRunAsync(action);
CompletionStage<R> thenCompose(fn);
CompletionStage<R> thenComposeAsync(fn);
从并行的And角度看:主要是 thenCombine、thenAcceptBoth 和 runAfterBoth 系列的接口
java
CompletionStage<R> thenCombine(other, fn);
CompletionStage<R> thenCombineAsync(other, fn);
CompletionStage<Void> thenAcceptBoth(other, consumer);
CompletionStage<Void> thenAcceptBothAsync(other, consumer);
CompletionStage<Void> runAfterBoth(other, action);
CompletionStage<Void> runAfterBothAsync(other, action);
从并行的OR角度看:主要是 applyToEither、acceptEither 和 runAfterEither 系列的接口
java
CompletionStage applyToEither(other, fn);
CompletionStage applyToEitherAsync(other, fn);
CompletionStage acceptEither(other, consumer);
CompletionStage acceptEitherAsync(other, consumer);
CompletionStage runAfterEither(other, action);
CompletionStage runAfterEitherAsync(other, action);
3.2.3 执行结束处理
主要是对正常结果的处理和异常结果的处理。上述提供的方法不允许抛出可检查型异常,但无法限制抛出运行时异常。CompletableFuture提供了优雅的方案来处理该问题:
java
CompletionStage exceptionally(fn);
CompletionStage<R> whenComplete(consumer);
CompletionStage<R> whenCompleteAsync(consumer);
CompletionStage<R> handle(fn);
CompletionStage<R> handleAsync(fn);
# 四、CompletableFuture原理
## 4.1 核心的类
**CompletableFuture类**定义:
```java
public class CompletableFuture<T>
extends Object
implements java.util.concurrent.Future<T>, java.util.concurrent.CompletionStage<T> {
volatile Object result; // Either the result or boxed AltResult
volatile Completion stack; // Top of Treiber stack of dependent actions
}
一个是Completion对象stack,这是一个CAS实现的无锁并发栈,每个链式调用的任务会被压入这个栈。另一个是Object对象result,这是当前CompletableFuture的结果
CompletionStage接口:CompletableStage用来表示异步过程中的一个阶段,它可以在另一个CompletableStage完成时做一些操作或计算,此接口中定义了一些基本的行为,通过这些行为组合可以简洁的描述非常复杂的任务。查看官方注释文档:CompletionStage是一个可能执行异步计算的"阶段",这个阶段会在另一个CompletionStage完成时调用去执行动作或者计算,一个CompletionStage会以正常完成或者中断的形式"完成",并且它的"完成"会触发其他依赖的CompletionStage。CompletionStage 接口的方法一般都返回新的CompletionStage,因此构成了链式的调用。
java
public interface CompletionStage<T> {
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync
(Function<? super T,? extends U> fn);
}
Comletion类定义:管理当前CompletableFuture的关系以及执行器
java
/** A Completion with a source, dependent, and executor. */
@SuppressWarnings("serial")
abstract static class UniCompletion<T,V> extends Completion {
Executor executor; // executor to use (null if none)
CompletableFuture<V> dep; // the dependent to complete 真正控制当前执行的流程
CompletableFuture<T> src; // source for action 触发它执行的源头
}
/* ------------- Base Completion classes and operations -------------- */
@SuppressWarnings("serial")
abstract static class Completion extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
volatile Completion next; // 下一个要执行的任务
}
4.2 原理分析
4.2.1 示例1
java
@Test
public void test9() throws ExecutionException, InterruptedException {
CompletableFuture<String> base = new CompletableFuture<>();
CompletableFuture<String> future = base.thenApply(s -> s + " 2").thenApply(s -> s + " 3");
base.complete("1");
System.out.println(future.get());
}
结果:1 2 3
可以看到:链式调用,依赖于dep,组成一条单链表,按照从首到尾的顺序执行。
4.2.2 示例2
java
@Test
public void test10() {
CompletableFuture<String> base = new CompletableFuture<>();
CompletableFuture<String> future =
base.thenApply(
s -> {
log.info("e");
return s + " 2";
});
base.thenAccept(s -> log.info("c")).thenAccept(aVoid -> log.info("d"));
base.thenAccept(s -> log.info("a")).thenAccept(aVoid -> log.info("b"));
base.complete("1");
}
结果:
22:24:26.725 [main] INFO com.example.hellodello.thread.CompletableFutureTest - a
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - b
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - c
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - d
22:24:26.727 [main] INFO com.example.hellodello.thread.CompletableFutureTest - e
4.2.3 通用调用链路
整体采用:CAS链式栈 + 单链表的链路设计
五、人物志
编程不识Doug Lea, 写尽Java也枉然 -- 来自微博
如果IT的历史,是以人为主体串接起来的话,那么肯定少不了Doug Lea。这个鼻梁挂着眼镜,留着德王威廉二世的胡子,脸上永远挂着谦逊腼腆笑容,服务于纽约州立大学Oswego分校计算机科学系的老大爷。他专门研究并发编程和并发数据结构的设计。
说他是这个世界上对Java影响力最大的个人,一点也不为过。因为两次Java历史上的大变革,他都间接或直接的扮演了举足轻重的角色。
- 贡献1:一次是由JDK 1.1到JDK 1.2,JDK1.2很重要的一项新创举就是Collections,其Collections的概念可以说承袭自Doug Lea于1995年发布的第一个被广泛应用的collections;
- 贡献2:一次是2004年所推出的Tiger。Tiger广纳了15项JSRs(Java Specification Requests)的语法及标准,其中一项便是JSR-166。JSR-166是来自于Doug编写的util.concurrent包
具体的示例:
java
-- 杰作HashMap;
-- java.util.concurrent 包;
-- AQS奠基者
社会评价:Doug是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己的知识并不会因为给了别人就减少了,知识的分享更能激荡出不一样的火花。《Effective JAVA》这本Java经典之作的作者Joshua Bloch便在书中特别感谢Doug Lea是此书中许多构想的共鸣板,感谢Doug Lea大方分享丰富而又宝贵的知识。