CompletableFuture 到底该怎么用:从方法、技巧、踩坑到源码
CompletableFuture 到底该怎么用:从方法、技巧、踩坑到源码 很多人第一次接触 CompletableFuture,感觉它很优雅:链式调用、异步编排、异常处理、多个任务聚合,看起来比手写线程池高级得多。 但真到项目里,问题往往不是“不会用”,而是“以为自己会用”。 比如: 为什么有的回调在主线程执行,有的又跑到了线程池里? 为什么 thenApply() 和 thenApplyAsync() 只差一个 Async,行为却可能完全不同? 为什么我明明用了异步,结果接口还是卡住了? 为什么 allOf() 执行完了,结果还得自己一个个 join() 去拿? 为什么异常被包成了 CompletionException,日志看起来特别恶心? 为什么线上机器 CPU 很高,最后发现是把阻塞 IO 全扔进了 ForkJoinPool.commonPool()? 这篇文章不打算只列 API,而是把 CompletableFuture 真正在工程里怎么用、怎么避坑、JDK 为什么这么设计,以及核心源码怎么运作,一次讲透。 一、先建立一个正确心智模型 很多人把 CompletableFuture 理解成“异步线程工具”,这个说法不算错,但太浅了。 更准确一点,它其实同时做了两件事: 它是一个 Future,代表“未来某个时刻会完成的结果”。 它是一个 CompletionStage,代表“这个结果完成之后,还能继续挂后续动作的可编排节点”。 也就是说,CompletableFuture 不只是“拿结果”,更重要的是“描述依赖关系”。 你可以把它想成一个节点: 节点里最终会有一个结果,或者一个异常。 节点完成后,会触发依赖它的后续节点。 节点和节点之间可以串行、并行、汇聚、竞争。 这才是它和传统 Future 最大的区别。 传统 Future 的典型写法是: Future<Order> future = executor.submit(this::queryOrder); Order order = future.get(); 问题在于: get() 是阻塞的。 不能优雅地声明“拿到订单后再查用户”。 多个 Future 的组合很难写。 而 CompletableFuture 的核心价值,就是把“等待结果”和“结果完成后的动作”拆开。 二、最常用的几类 API,到底该怎么分 1. 创建任务 最常见的是这两个: ...