连载中 19/21

CompletableFuture:异步编排的正确姿势

2026-11-01 · 13 阅读 · 0 评论 · 0 赞

Future 的痛点:只会干等

FutureTask 拿结果只有一条路:get() 阻塞等。任务之间没法串联、没法组合、异常要等到 get 才暴露——这是「发起后干等」,不是「编排」。CompletableFuture 把回调做成了流水线:上一步完成自动触发下一步,全程不阻塞调用线程。

三类回调:转换、消费、收尾

supplyAsync 提交有返回值的异步任务;thenApply 拿上一步结果做转换(有进有出);thenAccept 消费结果(有进无出);thenRun 上一步完成就执行(无进无出)。三个函数式口诀先记下,用法就顺了。更重要的是线程池:默认用 ForkJoinPool.commonPool——并行度约为核数减一,且全局共享。把 IO 任务塞进去,一个慢接口就能拖满整个 commonPool,殃及全 JVM 的并行流和异步任务。生产代码必须传自定义线程池:

ExecutorService pool = new ThreadPoolExecutor(...);   // 第 14 篇的手动姿势

CompletableFuture<String> f = CompletableFuture
        .supplyAsync(() -> queryUser(id), pool)      // IO 任务,指定自己的池
        .thenApply(this::enrich)                      // 有进有出:转换
        .thenAccept(this::save);                      // 有进无出:消费

组合:并行提速的模板

经典场景:页面聚合用户、订单、优惠券三个下游接口。串行:300 + 200 + 200 = 700ms;并行发起、全部完成后合并:只要最慢的 300ms:

CompletableFuture<User> u = supplyAsync(() -> userApi.get(id), pool);
CompletableFuture<List<Order>> o = supplyAsync(() -> orderApi.list(id), pool);
CompletableFuture<List<Coupon>> c = supplyAsync(() -> couponApi.list(id), pool);

CompletableFuture.allOf(u, o, c).join();      // 等三个全部完成
return assemble(u.join(), o.join(), c.join());

allOf 等全体、anyOf 等任意一个、thenCombine 把两个结果合并成第三个;join 与 get 的区别只在异常风格(join 抛非受检异常)。这段代码值得背下来——它就是异步聚合的标准模板。

链式与异常:thenCompose 与 exceptionally

上一步的结果要触发下一个异步任务(而不是同步转换)时,用 thenCompose——它把嵌套的 Future 拍平,避免「Future 里套 Future」的迷宫。异常沿链传播:exceptionally 兜底返回默认值;handle 无论成败都执行;whenComplete 只旁观不改结果。注意:异步任务的异常不会飞到调用方脸上,不处理就静默消失——和第 14 篇 submit 吞异常是同一个道理,整条链的末尾必须有兜底。

超时:别让异步变无底洞

JDK 9 起,CompletableFuture 支持 orTimeout(3, TimeUnit.SECONDS):超时后以 TimeoutException 收场,配合 exceptionally 转成降级值。下游接口再慢,用户体验也有上限。记住:没有超时的异步调用,是悬挂在线程池里的定时炸弹。

CompletableFuture 是线程池时代编排的天花板。但病根还在——任务里的 IO 阻塞照样占着宝贵的平台线程。下一篇看 JDK 21 怎么釜底抽薪:虚拟线程。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#Java#CompletableFuture#异步编排#JUC

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10113 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9876 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9306 阅读 · 21 评论 · 287 赞