Bladeren bron

docs:教程新增超时与异常链章节,补 orTimeout/completeOnTimeout/exceptionallyCompose 及速查表

yangyi 1 week geleden
bovenliggende
commit
2fc22cdd6f
1 gewijzigde bestanden met toevoegingen van 90 en 3 verwijderingen
  1. 90 3
      completablefuture-tutorial.md

+ 90 - 3
completablefuture-tutorial.md

@@ -336,7 +336,7 @@ public static void main(String[] args) throws Exception {
 
 ***
 
-## 八、手动完成:complete / completeExceptionally / obtrudeValue / cancel
+## 八、手动完成与状态查询:complete / completeExceptionally / obtrudeValue / isDone / cancel
 
 前面聊的都是"任务自己往结束跑",但异步世界里还有一种情况:**结果的给出,不由任务决定,而由外部决定**。比如某个阶段要等用户点击、等外部回调、等一个守护线程汇报(详见 `Cf08_ManualComplete`):
 
@@ -368,6 +368,21 @@ public static void main(String[] args) throws Exception {
     forced.obtrudeValue("强制更新的结果");
     System.out.println("obtrudeValue 覆盖后: " + forced.join());
 
+    // isDone: 查询任务是否"已结束"(成功、失败、取消都算 done)
+    CompletableFuture<String> pending = new CompletableFuture<>();
+    System.out.println("刚创建未完成 isDone: " + pending.isDone());
+    pending.complete("已赋值");
+    System.out.println("完成后 isDone: " + pending.isDone());
+
+    // obtrudeException: 与 obtrudeValue 成对,无论状态如何强制让其以异常结束(慎用)
+    CompletableFuture<String> forcedErr = CompletableFuture.completedFuture("旧结果");
+    forcedErr.obtrudeException(new RuntimeException("强制失败"));
+    try {
+        forcedErr.join();
+    } catch (Exception e) {
+        System.out.println("obtrudeException 后异常: " + e.getCause());
+    }
+
     // cancel: 取消任务, isCancelled 与 isCompletedExceptionally 都会为 true
     CompletableFuture<String> cancelling = new CompletableFuture<>();
     System.out.println("cancel() 返回值: " + cancelling.cancel(true));
@@ -376,7 +391,8 @@ public static void main(String[] args) throws Exception {
 }
 ```
 
-> 先说结论:`complete` 期待任务**还没有**完成;`obtrudeValue` 则不管状态**强制**覆盖,一般不要在生产乱用,否则正常逻辑都会被它"篡改"。
+> 先说结论:`complete` 期待任务**还没有**完成;`obtrudeValue` / `obtrudeException` 则不管状态**强制**覆盖(值或异常),一般不要在生产乱用,否则正常逻辑都会被它们"篡改"。
+> 状态查询三兄弟:`isDone`(不管成不成功、取不取消,只要结束就为 true)、`isCancelled`(被 cancel)、`isCompletedExceptionally`(以异常/取消结束)。
 
 **使用场景:**
 - 用 `new CompletableFuture<>()` 当"闸门/栅栏",等某个外部事件(消息推送、第三方回调、人工审核)到来后再 `complete`,让等待方放行。
@@ -455,7 +471,68 @@ thenApplyAsync(无池): ForkJoinPool.commonPool-worker-1
 
 ***
 
-## 十、总结与速查
+## 十、超时与异常链:orTimeout / completeOnTimeout / exceptionallyCompose
+
+异步调用最怕什么?**挂死**。下游接口慢、网络闪断,调用方却还在傻等。生产里的铁律是:一切异步外呼都必须有超时兜底。JDK 9 为此补了两个高频方法 `orTimeout` 和 `completeOnTimeout`,JDK 12 又补了 `exceptionallyCompose`(详见 `Cf10_TimeoutAndException`):
+
+```java
+public static void main(String[] args) throws Exception {
+    // orTimeout: 限时等待,超时后该 future 以 TimeoutException 失败。
+    // 注意:它并不会取消底层任务,慢任务仍会继续执行到最后
+    CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
+        sleep(1500);
+        System.out.println("底层慢任务执行完毕(并未被 orTimeout 取消)");
+        return "慢任务结果";
+    });
+    try {
+        slow.orTimeout(500, TimeUnit.MILLISECONDS).join();
+    } catch (Exception e) {
+        System.out.println("orTimeout 超时失败: " + e.getCause());
+    }
+    sleep(1600);   // 给慢任务一些执行时间,验证 orTimeout 不会中断它
+    System.out.println("此时 slow 状态 -> isDone: " + slow.isDone()
+            + ", isCompletedExceptionally: " + slow.isCompletedExceptionally());
+
+    // completeOnTimeout: 超时就给默认值完成,不抛异常,调用方拿到的就是兜底数据
+    CompletableFuture<String> withDefault = CompletableFuture.supplyAsync(() -> {
+        sleep(1500);
+        return "真实数据";
+    }).completeOnTimeout("超时兜底数据", 500, TimeUnit.MILLISECONDS);
+    System.out.println("completeOnTimeout 结果: " + withDefault.join());
+
+    // exceptionallyCompose: 异常后拼接一个"异步降级任务",结果自动展平
+    // 对照 thenCompose:一个是成功后的异步续接,一个是失败后的异步续接
+    CompletableFuture<String> recovered = CompletableFuture.supplyAsync(Cf10_TimeoutAndException::explode)
+            .exceptionallyCompose(ex -> CompletableFuture.supplyAsync(() -> "降级方案返回的数据"));
+    System.out.println("exceptionallyCompose 降级结果: " + recovered.join());
+}
+```
+
+**使用场景与时序总结:**
+
+> 上面跑出的时序大概是这样的:
+> - `orTimeout(500)`:500ms 一到,`slow` 立刻以 `TimeoutException` 失败,调用方马上拿到异常,不用干等 1.5s。但底层那个睡 1.5s 的任务**照常执行**,输出了"底层慢任务执行完毕"——`orTimeout` 只是"放弃等待",不是"杀掉任务"。
+> - `completeOnTimeout(500)`:500ms 一到直接补一个默认值完成,调用方拿到的是字符串"超时兜底数据",毫无惊险,也不用 try-catch。
+> - 一句话区分:**`orTimeout` 让调用方"快速失败",`completeOnTimeout` 让调用方"拿到兜底值"**。前者适合"拿不到就报错让上层重试/降级"的业务,后者适合"过期用默认数据也行"的业务(比如缓存兜底)。
+
+`exceptionallyCompose` 则补上了异常处理谱系的最后一块拼图:
+
+| 能力 | 正常路径 | 异常路径 |
+| --- | --- | --- |
+| 同步续接 | `thenApply` | `exceptionally` |
+| 异步续接 | `thenCompose` | `exceptionallyCompose` |
+| 观察兜底 | `whenComplete` / `handle` | — |
+
+> 想用 `exceptionally` 也"异步降级"?那它会返回一个嵌套的 `CompletableFuture<CompletableFuture<T>>`,又得手动展平;`exceptionallyCompose` 帮你把这一步解决掉。
+
+**使用场景:**
+- 所有对下游 RPC/HTTP/DB 的异步调用,一律 `orTimeout(2, SECONDS)` 类限时,配合 `exceptionally` 给出降级返回。
+- 读缓存/读配置这类"过期也不致命"的数据,用 `completeOnTimeout("默认值", ...)` 更省心。
+- 失败后的补救动作本身也是异步的(比如失败后发 MQ 补偿、调备用服务),用 `exceptionallyCompose`。
+
+***
+
+## 十一、总结与速查
 
 至此,`CompletableFuture` 的核心骨架我们都过了一遍。把它当作一张"异步流程图"来记:
 
@@ -464,6 +541,10 @@ thenApplyAsync(无池): ForkJoinPool.commonPool-worker-1
 | 无返回值异步任务 | `runAsync` | `Runnable` | `CompletableFuture<Void>` |
 | 有返回值异步任务 | `supplyAsync` | `Supplier` | `CompletableFuture<T>` |
 | 拿来即用 | `completedFuture` | — | `CompletableFuture<T>` |
+| 阻塞取结果(包装异常) | `join()` / `get()` | — | `T` |
+| 带超时取结果 | `get(超时, 单位)` | — | `T` |
+| 即取即用(未完成给默认值) | `getNow(value)` | — | `T` |
+| 是否已结束 | `isDone` | — | `boolean` |
 | 手拿上一个结果继续加工 | `thenApply` | `Function` | `CompletableFuture<U>` |
 | 消费上一个结果 | `thenAccept` | `Consumer` | `CompletableFuture<Void>` |
 | 等上一环跑完即可 | `thenRun` | `Runnable` | `CompletableFuture<Void>` |
@@ -479,6 +560,9 @@ thenApplyAsync(无池): ForkJoinPool.commonPool-worker-1
 | 失败时降级 | `exceptionally` | `Function` | `CompletableFuture<T>` |
 | 成功失败都观察(不吞异常) | `whenComplete` | `BiConsumer` | 不可改结果 |
 | 成功失败都可处理(可恢复) | `handle` | `BiFunction` | `CompletableFuture<U>` |
+| 异常后异步续接(展平) | `exceptionallyCompose` | `Function→CompletionStage` | `CompletableFuture<T>` |
+| 限时等待,超时快速失败 | `orTimeout` | — | `CompletableFuture<T>` |
+| 超时补默认值 | `completeOnTimeout` | — | `CompletableFuture<T>` |
 | 手动设定结果 | `complete` | — | `boolean` |
 | 手动以异常结束 | `completeExceptionally` | — | `boolean` |
 | 强制覆盖结果(慎用) | `obtrudeValue` | — | `void` |
@@ -492,6 +576,9 @@ thenApplyAsync(无池): ForkJoinPool.commonPool-worker-1
 4. 异常都会包装成 `CompletionException`,记得用 `getCause()` 取原始异常。
 5. 回调里不要 `join()/get()` 阻塞自己的执行线程。
 6. 非 Async 后缀的方法在"触发线程"上执行,别脑补它在公共池上跑。
+7. 对外异步调用一律配 `orTimeout`(失败可接受)或 `completeOnTimeout`(默认值可接受),避免调用方无限挂起。
+
+> **进阶冷门 API(用到再查 javadoc):** `delayedExecutor`(延迟提交任务)、`defaultExecutor`(查看默认执行器)、`minimalCompletionStage`(裁剪成最小 `CompletionStage`,只保留"依赖完成"能力)、`exceptionallyAsync`(异常降级也能指定线程池)。日常开发命中率很低,一两句话点到即可。
 
 ***