认识与使用Java中的异步编程CompletableFuture

🌟 一、什么是 CompletableFuture

✅ 基本定义

CompletableFuture<T> 是对 Future<T> 的增强版,实现了 异步编程函数式编程风格的任务编排

  • 它表示一个可能还没有完成的计算结果
  • 支持:
    • 异步执行任务
    • 链式调用(函数式风格)
    • 组合多个异步任务(thenCombineallOfanyOf
    • 异常处理
    • 回调通知(thenApplythenAcceptthenRun 等)

💡 简单说:它是“未来某个时刻会完成的任务”,你可以注册回调来处理结果或异常。

🛠️ 二、核心用法与 API 分类

一、创建异步任务

方法说明是否有返回值默认线程池
runAsync(Runnable)异步执行一个无返回值任务❌ 无ForkJoinPool.commonPool()
runAsync(Runnable, Executor)同上,但指定线程池❌ 无✅ 自定义
supplyAsync(Supplier<T>)异步执行并返回结果✅ 有ForkJoinPool.commonPool()
supplyAsync(Supplier<T>, Executor)同上,但指定线程池✅ 有✅ 自定义

🔥 建议:始终使用带 Executor 参数的版本,避免阻塞公共线程池。


二、链式处理结果(最常用)

这些方法都用于“前一个任务完成后,执行下一步”。区别在于是否返回值、是否异步执行

方法返回类型是否可转换结果是否异步执行执行线程来源
thenApply(Function<T,R>)CompletableFuture<R>✅ 转换结果❌ 同步(在前任务线程执行)前一个任务的线程
thenApplyAsync(Function<T,R>)CompletableFuture<R>✅ 转换结果✅ 异步commonPool
thenApplyAsync(Function<T,R>, Executor)CompletableFuture<R>✅ 转换结果✅ 异步✅ 指定线程池

关键区别

  • thenApply不新开线程,在前一个任务完成的线程中执行 → 可能阻塞流水线
  • thenApplyAsync强制异步,提交到线程池 → 更安全,但有调度开销

📌 何时用哪个?

  • 转换逻辑简单(如字符串处理)→ 用 thenApply
  • 转换耗时或涉及 IO → 用 thenApplyAsync(..., myExecutor)

类似方法对比:thenAccept vs thenRun

方法参数是否使用上一步结果是否返回值
thenAccept(Consumer<T>)使用上一步结果✅ 是❌ 无(返回 CompletableFuture<Void>
thenRun(Runnable)不使用上一步结果❌ 否❌ 无
CompletableFuture<String> result = CompletableFuture
    .supplyAsync(() -> "hello")                    // 第一步:返回字符串
    .thenApply(s -> s + " world")                  // 第二步:加工
    .thenApply(String::toUpperCase)                // 第三步:转大写
    .thenApply(s -> s + "!");

result.thenAccept(System.out::println).join();
// 输出:HELLO WORLD!





future.thenAccept(res -> System.out.println("结果是:" + res)); // 可以用 res
future.thenRun(() -> System.out.println("任务完成了"));         // 拿不到 res

💡 类比:

  • thenAccept → “拿到结果后做点事”
  • thenRun → “不管结果,只要完成了就做点事”

三、组合多个任务

1. thenCombine vs thenCompose vs thenAcceptBoth

方法用途是否组合结果典型场景
thenCombine(CompletableFuture<U>, BiFunction<T,U,R>)两个任务都完成后,合并结果并返回新值✅ 是获取价格 + 税率 → 计算总价
thenCompose(Function<T, CompletableFuture<U>>)当前任务结果用于启动下一个异步任务(扁平化嵌套)✅ 是(链式异步)根据用户ID查用户 → 再查订单(异步查)
thenAcceptBoth(CompletableFuture<U>, BiConsumer<T,U>)两个任务都完成后,消费两个结果,无返回值❌ 否打印日志、发通知

🔥 重点区别

  • thenCombine:两个独立异步任务 → 合并结果
  • thenCompose异步中的异步,类似 flatMap,避免 CompletableFuture<CompletableFuture<T>>

CompletableFuture<Integer> priceFuture = CompletableFuture.supplyAsync(() -> {
    sleep(1000);
    return 100;
});

CompletableFuture<String> taxFuture = CompletableFuture.supplyAsync(() -> {
    sleep(800);
    return "13%";
});

CompletableFuture<String> combined = priceFuture.thenCombine(taxFuture, (price, tax) ->
    "价格:" + price + ", 税率:" + tax
);

combined.thenAccept(System.out::println).join();
// 输出:价格:100, 税率:13%




// thenCompose 示例:避免嵌套
userService.findById(id)
    .thenCompose(user -> orderService.findByUser(user)) // 返回 CompletableFuture<Order>
    .thenAccept(order -> ...);

2. allOf vs anyOf

方法行为返回类型注意事项
CompletableFuture.allOf(CompletableFuture<?>... futures)所有任务都完成才算完成CompletableFuture<Void>❗ 拿不到结果,需手动 .join() 每个 future
CompletableFuture.anyOf(CompletableFuture<?>... futures)任一任务完成即完成CompletableFuture<Object>❗ 返回 Object,需转型;其他任务仍在运行

// allOf 后获取所有结果

CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> System.out.println("任务1完成"));
CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> System.out.println("任务2完成"));
CompletableFuture<Void> f3 = CompletableFuture.runAsync(() -> System.out.println("任务3完成"));

CompletableFuture<Void> allDone = CompletableFuture.allOf(f1, f2, f3);

allDone.thenRun(() -> System.out.println("全部完成!")).join();

//或者

List<String> results = CompletableFuture.allOf(f1, f2, f3)
    .thenApply(v -> Stream.of(f1, f2, f3).map(CompletableFuture::join).toList())
    .join();


//AnyOf
CompletableFuture<String> fastest = CompletableFuture.anyOf(
    CompletableFuture.supplyAsync(() -> { sleep(2000); return "慢的"; }),
    CompletableFuture.supplyAsync(() -> { sleep(500); return "快的"; })
).thenApply(o -> "获胜者:" + o);

fastest.thenAccept(System.out::println).join();
// 输出:获胜者:快的

四、异常处理 API 对比

方法何时调用是否改变结果类型是否继续传递异常
exceptionally(Function<Throwable, T> fallback)仅当前阶段出错时调用✅ 可返回默认值❌ 被捕获,后续不再抛
handle(BiFunction<T, Throwable, R>)无论成功或失败都调用✅ 可统一处理结果和异常✅ 可选择重新抛出
whenComplete(BiConsumer<T, Throwable>)无论成功或失败都调用❌ 不改变结果(仅消费)✅ 异常继续向后传播

🔥 关键区别

  • exceptionally只处理异常,类似 catch
  • handle既能处理成功也能处理失败,可做转换,类似 map + catch
  • whenComplete仅用于清理或日志,不能改变结果,异常会继续抛
future
    .thenApply(this::process)
    .handle((result, ex) -> {
        if (ex != null) {
            log.warn("处理失败,使用默认值", ex);
            return DefaultValue;
        }
        return result;
    });

五、执行线程模型总结(最容易踩坑!)

方法执行线程来源
thenApplythenAcceptthenRun❗ 前一个任务所在线程(可能阻塞流水线)
thenApplyAsync 等 Async 版本✅ ForkJoinPool.commonPool() 或指定 Executor
supplyAsync() 无参commonPool
supplyAsync(executor)✅ 指定线程池

⚠️ 大坑警告: 如果你在 thenApply 中做了耗时操作(如 sleep、DB 查询),它会阻塞前一个任务的线程,可能导致线程饥饿。

最佳实践

// 始终使用自定义线程池 + Async 版本
CompletableFuture.supplyAsync(() -> fetchUser(), myExecutor)
    .thenApplyAsync(user -> enrichUser(user), myExecutor)
    .thenAcceptAsync(user -> save(user), myExecutor);

六、API 语义速查表(一句话总结)

API一句话用途
supplyAsync异步执行一个有返回值的任务
thenApply上一步结果拿来转换成新值(同步执行)
thenApplyAsync同上,但异步执行(推荐)
thenAccept拿到结果后消费(如打印、存储)
thenRun不关心结果,只关心“完成”这件事
thenCombine两个异步任务都完成,合并结果
thenCompose上一步结果用于启动下一个异步任务(扁平化)
exceptionally出错时提供默认值(兜底)
handle成功和失败都能处理,可统一转换
whenComplete成功或失败都执行(如关闭资源、打日志)
allOf所有任务完成才算完成(注意拿不到结果)
anyOf任一任务完成就完成(注意返回 Object)

✅ 总结:如何选择?

你要做什么推荐 API
异步获取数据supplyAsync(task, executor)
处理结果并返回新值thenApplyAsync(func, executor)
只消费结果(如打印)thenAcceptAsync(consumer, executor)
任务完成后发通知thenRunAsync(runnable, executor)
合并两个独立异步结果thenCombine(other, combiner)
异步依赖(A 结果 → B 任务)thenCompose(func)
出错返回默认值exceptionally(fallback)
统一处理成功/失败handle((res, ex) -> ...)
清理资源或打日志whenComplete((res, ex) -> ...)
等待所有任务完成CompletableFuture.allOf(...).join()
获取最快结果CompletableFuture.anyOf(...).join()

🎯 三、最佳实践建议

  1. 始终使用自定义线程池

    ExecutorService executor = Executors.newFixedThreadPool(10);
    CompletableFuture.supplyAsync(task, executor);
  2. 链式调用中显式指定线程池

    future.thenApplyAsync(transformer, executor);
  3. 避免在 thenApply 中做耗时同步操作

    • 否则会阻塞整个异步流水线
  4. 合理使用 timeout(需手动实现)

    CompletableFuture.orTimeout(future, 3, TimeUnit.SECONDS);
    // 或自己写:applyToEither(CompletableFuture.delayedExecutor(...))
  5. 监控与日志

    • 记录任务开始/结束时间
    • 打印线程名便于排查

🏁 总结

特性说明
✅ 优势简化异步编程、支持函数式链式调用、强大的组合能力
❌ 劣势学习曲线陡、线程模型复杂、异常处理易忽略
🎯 适用场景Web 请求聚合、IO 密集型任务、微服务调用编排、数据加载优化
🚫 不适用场景简单同步任务、CPU 密集型且无并行需求
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值