Skip to content

Latest commit

 

History

History
221 lines (172 loc) · 11.8 KB

completable-future-guide.md

File metadata and controls

221 lines (172 loc) · 11.8 KB

👥 CompletableFuture Guide



🎯 目标

  • 完备说明CompletableFuture的使用方式
  • 给出 最佳实践建议 与 使用陷阱注意
  • 期望在业务中,更有效安全地使用CompletableFuture

为了阅读的简洁方便,后文CompletableFuture会简写成CF

🔠 CF并发执行的描述及其用语

cf-graph

基本概念与术语:

  • 任务(Task)/ 计算(Computation
    • 任务逻辑(Task Logic)/ 业务逻辑(Biz Logic
    • 执行(Execute)任务
  • 状态(State
    • 运行中(Running〚1〛
    • 取消(Cancelled〚2〛
    • 完成(Completed / Done
      • 成功(Success / Successful)/ 正常完成(Completed Normally)/ 成功完成(Completed Successfully
      • 失败(Failed / Fail)/ 异常完成(Completed Exceptionally
  • 状态转变(Transition
    • 事件(Event)、触发(Trigger
  • 业务流程(Biz Flow)、CF链(Chain
    • 流程图(Flow Graph)、有向无环图 / DAG
      • 为什么构建的CF链一定是DAG
    • 流程编排(Flow Choreography
  • 前驱(Predecessor)/ 后继(Successor
    • 上游任务 / 前驱任务 / Dependency Task(我依赖的任务)
    • 下游任务 / 后继任务 / Dependent Task(依赖我的任务)

注:上面用/隔开的多个词是,在表述CF同一个概念时,会碰到的多个术语;在不影响理解的情况下,后文会尽量统一用第一个词来表达。

task stauts transition

更多说明:

  • 〚1〛 任务状态有且有只有 运行中(Running)、取消(Cancelled)、完成(Completed)这3种状态。
    • 对于「完成」状态,进一步可以分成 成功(Success)、失败(Failed)2种状态。
  • 所以也可以说,任务状态有且只有 运行中、取消、成功、失败 这4种状态。
    • 右图是任务的状态及其转变图。
    • 在概念上CF的状态转变只能是单次单向的,这很简单可靠、也容易理解并和使用直觉一致。
    • 注:虽然下文提到的obtrudeValue()/obtrudeException方法可以突破CF概念上的约定,但这2个后门方法在正常设计实现中不应该会用到,尤其在业务使用应该完全忽略;带来的问题也由使用者自己了解清楚并注意。

  • 〚2〛 关于「取消」状态:
  • 对于「取消」状态,或说设置了「CancellationException」失败异常的CompletableFuture cf,相比其它异常失败 / 设置了其它失败异常 的情况,不一样的地方:
    • 调用cf.get() / cf.get(timeout, unit)方法
      • 会抛出CancellationException异常
      • 其它异常失败时,这2个方法抛出的是包了一层的ExecutionExceptioncause是实际的失败异常
    • 调用cf.join() / cf.getNow(valueIfAbsent)方法
      • 会抛出CancellationException异常
      • 其它异常失败时,这2个方法抛出的是包了一层的CompletionExceptioncause是实际的失败异常
    • 调用cf.exceptionNow()方法
      • 会抛出IllegalStateException,而不是返回cf所设置的CancellationException异常
      • 其它异常失败时,exceptionNow()返回设置的异常
    • 调用cf.isCancelled()方法
      • 返回true
      • 其它异常失败时,isCancelled()返回false
  • 其它地方,CancellationException异常与其它异常是一样处理的。比如:
    • 调用cf.resultNow()方法
      都是抛出IllegalStateException异常
    • 调用cf.isDone()cf.isCompletedExceptionally()
      都是返回true
    • CompletionStage接口方法对异常的处理,如
      cf.exceptionally()的方法参数Function<Throwable, T>所处理的都是直接设置的异常对象没有包装过

🕹️ CF并发执行的关注方面

CF任务执行/流程编排,即执行提交的代码逻辑/计算/任务,涉及下面4个方面:

  • 任务的输入输出
    • CF所关联任务的输入参数/返回结果(及其数据类型)
  • 任务的调度,即在哪个线程来执行任务。可以是
    • 在触发的线程中就地连续执行任务
    • 在指定Executor(的线程)中执行任务
  • 任务的错误处理(任务运行出错)
  • 任务的超时控制
    • 超时控制是并发的基础关注方面之一
    • 到了Java 9提供了内置支持,新增了completeOnTimeout(...)/orTimeout(...)方法

本节「并发关注方面」,会举例上一些CF方法名,以说明CF方法的命名模式;
可以先不用关心方法的具体功能,在「CF的功能介绍」中会分类展开说明CF方法及其功能。

1. 输入输出

对应下面4种情况:

  • 无输入无返回(00)
    • 对应Runnable接口(包含单个run方法)
  • 无输入有返回(01)
    • 对应Supplier<O>接口(包含单个supply方法)
  • 有输入无返回(10)
    • 对应Consumer<I>接口(包含单个accept方法)
  • 有输入有返回(11)

注:

  • 对于有输入或返回的接口(即除了Runnable接口)
    • 都是泛型的,所以可以支持不同的具体数据类型
    • 都是处理单个输入数据
    • 如果要处理两个输入数据,即有两个上游CF的返回,会涉及下面的变体接口
  • 对于有输入接口,有两个输入参数的变体接口:

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. 调度

任务调度是指,任务在哪个线程执行。有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)

3. 错误处理

提交给CF的任务可以运行出错(抛出异常),即状态是失败(Failed)或取消(Cancelled)。

对于直接读取结果的方法:

  • 读取 成功结果的方法,如 cf.get()cf.join()会抛出异常(包装的异常)来反馈
  • 读取 失败结果的方法,如 cf.exceptionNow()会返回结果异常或是抛出异常来反馈

对于CompletionStage接口中编排执行的方法,会根据方法的功能 是只处理成功结果或失败结果一者,或是同时处理成功失败结果二者。如

  • exceptionally(...)只处理 失败结果
  • whenComplete(...)/handle(...)同时处理 成功与失败结果;
    • 这2个方法的参数lamdbaBiConsumer/BiFunction)同时输入成功失败结果2个参数:valueexception
  • 其它多数的方法只处理 成功结果
  • 对于不处理的结果,效果上就好像
    没有调用这个CompletionStage方法一样,即短路bypass了 👏

4. 任务执行的超时控制

超时控制是并发的基础关注方面之一。

到了Java 9提供了内置支持,新增了completeOnTimeout(...)/orTimeout(...)方法。

CF的超时控制,在实现上其实可以看成是CF的使用方式,并不是CF要实现基础能力;即可以通过其它已有的CF功能,在CF外围实现。

🔧 CF的功能介绍 | 💪 CF方法分类说明

见子文档页 cf-functions-intro.md

CF的方法个数比较多,所以介绍内容有些多,内容继续完善中… 💪 💕

📐 CF的设计模式 | 🐻 最佳实践与使用陷阱

见子文档页 cf-design-patterns.md

还没有什么内容,收集思考展开中… 💪 💕