CompletableFuture任务顺序由依赖结构决定而非代码顺序:thenApply/thenAccept/thenRun实现串行;thenCompose扁平化嵌套异步;allOf/anyOf协调并行;handle/whenComplete/exceptionally处理异常路径。

CompletableFuture 的任务顺序控制,核心在于理解它的链式调用模型和依赖关系建模方式——它不靠“写代码的先后顺序”决定执行顺序,而是靠方法返回的新 CompletableFuture 的依赖结构来决定。你写的代码顺序只是构建这个依赖图的过程。
用 thenApply / thenAccept / thenRun 串行执行
这是最直接的顺序控制:前一个任务完成(正常结束),才触发下一个任务。
- thenApply:接收上一个结果,返回新值(有返回值,可继续链式)
- thenAccept:接收上一个结果,不返回(消费型,链中断,除非再用 thenRun 等续上)
- thenRun:不关心上一个结果,只等它完成就执行(无参 Runnable)
示例:
CompletableFuture<String> step1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<Integer> step2 = step1.thenApply(s -> s.length()); // 等 step1 完成后执行
CompletableFuture<Void> step3 = step2.thenAccept(len -> System.out.println("length: " + len));
这三步严格按 step1 → step2 → step3 顺序执行,且 step2 必须等 step1 返回值,step3 等 step2 完成。
立即学习“Java免费学习笔记(深入)”;
用 thenCompose 实现真正的“扁平化”串行
当你在回调里又创建了一个新的 CompletableFuture(比如嵌套异步调用),直接用 thenApply 会得到 CompletableFuture<CompletableFuture<T>>,造成“两层包装”。这时要用 thenCompose,它自动“展平”一层,让外层等待内层真正完成。
- 错误写法(嵌套 CF,顺序没断但类型臃肿、难管理):
cf.thenApply(x -> asyncCall(x)) - 正确写法(自然衔接两个异步步骤):
cf.thenCompose(x -> asyncCall(x))
示例:
CompletableFuture<String> loadUser = CompletableFuture.supplyAsync(() -> "user123");
CompletableFuture<String> fetchProfile = loadUser.thenCompose(uid ->
CompletableFuture.supplyAsync(() -> "profile of " + uid)
);
fetchProfile 只有在 loadUser 完成 且 内部 supplyAsync 执行完后才算完成——这才是跨异步步骤的真正顺序保障。
用 allOf / anyOf 协调多个并行任务的完成时机
当你要等多个独立任务全部完成或任意一个完成就行动,就不能靠链式,而要用组合器:
-
CompletableFuture.allOf(cf1, cf2, cf3):返回一个 CF,等所有入参 CF 都完成(注意:它返回
CompletableFuture<Void>,不携带结果;要取结果得手动 get 每个原始 CF) - CompletableFuture.anyOf(cf1, cf2, cf3):返回一个 CF,只要任一入参完成就结束(结果是 Object,需 cast,且可能含异常)
若需汇总结果,推荐先用 allOf 等完成,再用 thenApply 统一收集:
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> b = CompletableFuture.supplyAsync(() -> "B");
CompletableFuture<Void> all = CompletableFuture.allOf(a, b);
CompletableFuture<List<String>> combined = all.thenApply(v ->
Arrays.asList(a.join(), b.join())
);
异常与顺序的交叉处理:handle / whenComplete / exceptionally
顺序控制不能只考虑成功路径。一旦某步抛异常,后续 thenXXX 默认不会执行(被跳过)。如需无论成败都执行清理或记录,用:
- whenComplete((result, ex) -> {...}):始终执行,不改变结果或异常(适合日志、资源释放)
- handle((result, ex) -> {...}):始终执行,并可返回新结果或抛新异常(用于兜底转换)
- exceptionally(ex -> {...}):仅异常时执行,返回替代结果(类似 try-catch 的 catch 块)
它们都保持原有执行链顺序,只是插入了异常响应逻辑,不影响前置任务的完成时机判断。


















