🚧 项目还在开发中没有发布。
工作项列表及其进展,参见 issue 6。
如何管理并发执行是个复杂易错的问题,业界有大量的工具、框架可以采用。
并发工具、框架的广度了解,可以看看如《七周七并发模型》、《Java虚拟机并发编程》、《Scala并发编程(第2版)》;更多关于并发主题的书籍参见书单。
其中CompletableFuture (CF)
有其优点:
Java
标准库内置- 无需额外依赖,几乎总是可用
- 相信有极高的实现质量
- 广为人知广泛使用,有一流的群众基础
CompletableFuture
在2014年发布的Java 8
提供,有~10年了CompletableFuture
的父接口Future
早在2004年发布的Java 5
中提供,有~20年了- 虽然
Future
接口不支持 执行结果的异步获取与并发执行逻辑的编排,但也让广大Java
开发者熟悉了Future
这个典型的概念与工具
- 功能强大、但不会非常庞大复杂
- 高层抽象
- 或说 以业务流程的形式表达技术的并发流程
- 可以不使用繁琐易错的基础并发协调工具,如
CountDownLatch
、锁(Lock
)、信号量(Semaphore
)
和其它并发工具、框架一样,CompletableFuture
用于
- 并发执行业务逻辑,或说编排并发的处理流程/处理任务
- 利用多核并行处理
- 提升业务响应性
值得更深入了解和应用。 💕
- 作为文档库:
- 完备说明
CompletableFuture
的使用方式 - 给出 最佳实践建议 与 使用陷阱注意
- 期望在业务中,更有效安全地使用
CompletableFuture
- 这部分是主要目标
- 完备说明
- 作为代码库:
- 补齐在业务使用中
CompletableFuture
所缺失的功能 - 期望在业务中,更方便自然地使用
CompletableFuture
- 这部分只是甜点目标
- 补齐在业务使用中
WIP...
为了阅读的简洁方便,后文
CompletableFuture
会简写成CF
。
WIP...
基本概念与术语:
- 任务(
Task
)/计算(Computation
)/执行(Execution
)- 任务逻辑(
Task Logic
)/业务逻辑(Biz Logic
)
- 任务逻辑(
- 状态(
State
)- 运行中(
Running
) - 取消(
Cancelled
) - 完成(
Completed
/Done
)- 成功(
Successed
/Success
)/正常完成(Completed Normally
)/成功完成(Completed Successfully
) - 失败(
Failed
/Fail
)/异常完成(Completed Exceptionally
)
- 成功(
- 运行中(
- 业务流程(
Biz Flow
)、CF
链(Chain
)- 流程图(
Flow Graph
)、有向无环图/DAG
- 为什么构建的
CF
链一定是DAG
?
- 为什么构建的
- 流程编排(
Flow Choreography
)
- 流程图(
- 前驱(
Predecessor
)/后继(Successor
)- 上游任务/前驱任务/
Dependency Task
(我依赖的任务) - 下游任务/后继任务/
Dependent Task
(依赖我的任务)
- 上游任务/前驱任务/
- 状态变化、事件(
Event
)、触发(Trigger
)
CF
任务执行/流程编排,即执行提交的代码逻辑/计算/任务,涉及下面4个方面:
- 任务的输入输出
- 即
CF
所关联任务的输入参数/返回结果(及其数据类型)
- 即
- 任务的调度,即在哪个线程来执行任务。可以是
- 在触发的线程中就地连续执行任务
- 在指定
Executor
(的线程)中执行任务
- 任务的错误处理(任务执行报错)
- 任务的超时处理
- 处理超时是并发的基础关注方面之一
- 在实现上可以看成
CF
的使用方式 Java 9
通过新增的completeOnTimeout(...)/orTimeout(...)
方法提供了内置支持
本节『并发关注方面』会举例一些
CF
方法名,以说明CF
方法的命名模式,可以先不用关心方法的具体功能。
在下一节『CF
的功能』会分类展开说明CF
方法的功能。
对应下面4种情况:
- 无输入无返回(00)
- 对应
Runnable
接口(包含单个run
方法)
- 对应
- 无输入有返回(01)
- 对应
Supplier<O>
接口(包含单个supply
方法)
- 对应
- 有输入无返回(10)
- 对应
Consumer<I>
接口(包含单个accept
方法)
- 对应
- 有输入有返回(11)
- 对应
Function<I, O>
接口(包含单个apply
方法)
- 对应
注:
- 对于有输入或返回的接口(即除
Runnable
接口)- 都是泛型的,所以可以支持不同的具体数据类型
- 都是处理单个输入数据
- 如果要处理两个输入数据,即有两个上游
CF
的返回,会涉及下面的变体接口
- 对于有输入接口,有两个输入参数的变体接口:
Consumer
接口的两参数变体接口:BiConsumer<I1, I2>
Function
接口的两参数变体接口:BiFunction<I1, I2, O>
CF
通过其方法名中包含的用词来体现:
run
:无输入无返回(00)- 即是
Runnable
接口包含的run
方法名 - 相应的
CF
方法名的一些例子:runAsync(Runnable runnable)
thenRun(Runnable action)
runAfterBoth(CompletionStage<?> other, Runnable action)
runAfterEitherAsync(CompletionStage<?> other, Runnable action)
- 即是
supply
:无输入有返回(01)- 即是
Supplier
接口包含的supply
方法名 - 相应的
CF
方法名的一些例子:supplyAsync(Supplier<U> supplier)
supplyAsync(Supplier<U> supplier, Executor executor)
- 即是
accept
:有输入无返回(10)- 即是
Consumer
接口包含的accept
方法名 - 相应的
CF
方法名的一些例子:thenAccept(Consumer<T> action)
thenAcceptAsync(Consumer<T> action)
thenAcceptBoth(CompletionStage<U> other, BiConsumer<T, U> action)
acceptEitherAsync(CompletionStage<T> other, Consumer<T> action)
- 即是
apply
:有输入有返回(11)- 即是
Function
接口包含的apply
方法名。CF
的方法如 - 相应的
CF
方法名的一些例子:thenApply(Function<T, U> fn)
thenApplyAsync(Function<T, U> fn)
applyToEither(CompletionStage<T> other, Function<T, U> fn)
- 即是
任务调度是指,任务在哪个线程执行。有2种方式:
- 在触发的线程中就地连续执行任务
- 在指定
Executor
(的线程)中执行任务
CF
通过方法名后缀Async
来体现调度方式:
- 有方法名后缀
Async
:- 在触发
CF
后,任务在指定Executor
执行- 如果不指定
executor
参数,缺省是ForkJoinPool.commonPool()
- 如果不指定
- 相应的
CF
方法名的一些例子:runAsync(Runnable runnable)
thenAcceptAsync(Consumer<T> action, Executor executor)
runAfterBothAsync(CompletionStage<?> other, Runnable action)
- 在触发
- 无方法名后缀
Async
:- 任务在触发线程就地连续执行
- 相应的
CF
方法名的一些例子:thenAccept(Consumer<T> action)
thenApply(Function<T, U> fn)
**applyToEither(CompletionStage<T> other, Function<T, U> fn)
WIP...
WIP...
通过静态工厂方法(🅵actory)或构造函数(🅒onstructor)来创建CompletableFuture
。这些方法是CompletableFuture
链的起始。
Method Name | 🅒/🅵 | 结果类型 | Executor |
|
---|---|---|---|---|
completedFuture(U value) |
🅵 | U |
无需 | 用入参value 直接创建一个已完成的CF ,无需Executor 来运行 |
completedStage(U value) J9 |
🅵 | U |
无需 | 与上一方法一样,只是返回的类型是CompletionStage 而不CF |
failedFuture(Throwable ex) J9 |
🅵 | U |
无需 | 用入参ex 直接创建一个已完成的CF ,无需Executor 来运行 |
failedStage(Throwable ex) J9 |
🅵 | U |
无需 | 与上一方法一样,只是返回的类型是CompletionStage<U> 而不CF |
supplyAsync(Supplier<U> supplier) |
🅵 | U |
CF 缺省Executor |
|
supplyAsync(Supplier<U> supplier, Executor executor) |
🅵 | U |
executor 入参 |
|
runAsync(Runnable runnable) |
🅵 | Void |
CF 缺省Executor |
|
runAsync(Runnable runnable, Executor executor) |
🅵 | Void |
executor 入参 |
|
allOf(CompletableFuture<?>... cfs) 〚1〛 |
🅵 | Void |
无需 | 组合输入的多个CF ,本身无执行逻辑,所以无需Executor |
anyOf(CompletableFuture<?>... cfs) 〚1〛 |
🅵 | Void |
无需 | 同上 |
CompletableFuture<T>() 〚2〛 |
🅒 | T |
无需 | 显式通过CF 对象的写方法完成,无需Executor 来运行 |
注:
- 〚1〛:
allOf
/anyOf
这个2个方法虽然是静态工厂方法;但不是CF
链的起点,而是输入多个CF
,用于编排多路的流程。- 在功能与使用的上,应该和下面【3. 流程编排】一节的方法归类在一起。
- 这2个方法也列在上面的表格,只是为了体现出是静态工厂方法这个特点。
- 〚2〛:在日常的业务开发中使用
CF
来编排业务流程,几乎一定不应该使用 这个构造方法。- 构造函数创建的
CF
的使用场景:在已有异步处理线程,即不与CF
关联的Executor
,显式调用CF
对象的写方法设置其它结果; - 往往是在中间件中会有必要这样使用,比如在网络
IO
框架的回调(线程)中完成处理后设置CF
结果。
- 构造函数创建的
读方法:
Method Name | 所属父接口 | 阻塞? | |
---|---|---|---|
boolean isDone() |
Future |
||
T get() |
Future |
阻塞❗ | |
T get(long timeout, TimeUnit unit) |
Future |
阻塞❗〚1〛 | |
T getNow(T valueIfAbsent) |
- | ||
T resultNow() J19 |
Future |
返回已正常完成CF 的正常结果;如果CF 不是正常完成(未完成/被取消/异常完成)则抛出IllegalStateException 异常 |
|
T join() |
- | 阻塞❗️ | |
boolean isCompletedExceptionally() |
- | ||
Throwable exceptionNow() J19 |
Future |
返回已异常完成CF 的出错异常;如果CF 不是异常完成(未完成/被取消/正常完成)则抛出IllegalStateException 异常 |
|
boolean isCancelled() |
- | ||
State state() J19 |
Future |
注:
- 〚1〛:
T get(long timeout, TimeUnit unit)
如果设置的超时是0
,不会BLOCKING;但这个情况下应该调用T getNow(T valueIfAbsent)
。
写方法:
Method Name | 所属父接口 | 阻塞? | |
---|---|---|---|
boolean complete(T value) |
- | ||
completeAsync(Supplier<T> supplier) J9 |
- | 方法返回this ,方便链式调用 |
|
completeAsync(Supplier<T> supplier, Executor executor) J9 |
- | 同上 | |
boolean completeExceptionally(Throwable ex) |
- | ||
exceptionallyAsync(Function<Throwable, ? extends T> fn) |
- | ||
boolean cancel(boolean mayInterruptIfRunning) |
Future |
||
void obtrudeValue(T value) |
- | ||
void obtrudeException(Throwable ex) |
- |
WIP...
Method Name | 所属父接口 | 阻塞? | |
---|---|---|---|
completeOnTimeout(T value, long timeout, TimeUnit unit) J9 |
- | ||
orTimeout(long timeout, TimeUnit unit) J9 |
- | ||
delayedExecutor(long delay, TimeUnit unit, Executor executor) J9 |
- |
WIP...
从CF
的功能使用上,这些方法不是必须的。
但通过这些CF
的非功能方法可以
- 提升实现的安全性
- 如防御式拷贝防止被使用方意外写结果
- 获取额外信息
- 如用于监控
- ……
Method Name | 结果类型 | |
---|---|---|
CompletableFuture<T> copy() |
T |
|
CompletableFuture<U> newIncompleteFuture() J9〚1〛 |
T |
|
CompletionStage<T> minimalCompletionStage() J9〚1〛 |
T |
|
Executor defaultExecutor() J9 |
- | |
int getNumberOfDependents() |
- |
注:
- 〚1〛:
CompletableFuture<U> newIncompleteFuture()
功能与CompletableFuture<T>()
是一样,实际上代码实现就只是调用构造函数。- 相比构造函数,工厂方法形式的一个好处是可以无需指定泛型参数;在很多库的
API
中都可以看到这样的设计方式。
- 相比构造函数,工厂方法形式的一个好处是可以无需指定泛型参数;在很多库的
WIP...
WIP...
会形成(池型)死锁。
WIP...
WIP...
提供在业务使用中CompletableFuture
所缺失的功能。
- 运行多个
CompletableFuture
并返回结果的allOf
方法:resultAllOf
方法,运行多个相同结果类型的CompletableFuture
CompletableFuture<List<T>> resultAllOf(CompletableFuture<T>... cfs)
CompletableFuture<List<T>> resultAllOf(List<? extends CompletableFuture<T>> cfs)
resultOf
方法,运行多个不同结果类型的CompletableFuture
CompletableFuture<Pair<T1, T2>> resultOf(CompletableFuture<T1> cf1, CompletableFuture<T2> cf2)
CompletableFuture<Triple<T1, T2, T3>> resultOf(CompletableFuture<T1> cf1, CompletableFuture<T2> cf2, CompletableFuture<T3> cf3)
- 类型安全的
anyOf
方法:- 提供的方法:
CompletableFuture<T> anyOf(CompletableFuture<T>... cfs)
CompletableFuture<T> anyOf(List<? extends CompletableFuture<T>> cfs)
CF
返回的类型是Object
,丢失具体类型:CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
- 提供的方法:
cffu
是 CompletableFuture-Fu
的缩写;读作C Fu
,谐音Shifu/师傅
。
嗯嗯,想到了《功夫熊猫》里可爱的小浣熊师傅吧~ 🦝