future.get() 会阻塞主线程,因无超时机制且不区分任务失败或未完成;应使用带超时的get()、捕获executionexception和timeoutexception,或避免调用以保持异步价值。

Future.get() 为什么总卡住主线程
因为 get() 是阻塞调用,没结果就一直等,哪怕后台任务其实失败了。常见于用 ExecutorService.submit() 提交任务后直接调用 get(),结果整个线程挂起,尤其在 Servlet 容器或 Spring WebMvc 里容易拖垮请求线程池。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 永远给
get()加超时:用get(3, TimeUnit.SECONDS),别用无参版本 - 捕获
ExecutionException(包装了实际异常)和TimeoutException,否则连失败原因都看不到 - 如果只是想“发出去就不管”,别调
get()——Future本身不提供回调,硬等等于放弃异步价值
CompletableFuture.supplyAsync() 和 new Thread() 的本质区别
supplyAsync() 默认用 ForkJoinPool.commonPool(),不是随便起个线程。这个池子大小通常等于 CPU 核心数,且**不接受自定义线程名、无法设置守护线程、拒绝策略固定为抛异常**。而 new Thread() 完全可控,但得自己管生命周期和复用。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- IO 密集型任务(比如 HTTP 调用、DB 查询)别依赖
commonPool(),改用自定义线程池:supplyAsync(() -> api.call(), myIoPool) - 自定义池必须显式
shutdown(),否则 JVM 无法退出 ——commonPool()是 JVM 级共享的,不用关 -
supplyAsync()的 lambda 里千万别 throw 检查异常,它只接受RuntimeException,否则编译不过
thenApply() 和 thenAccept() 什么时候会不执行
两个方法都属于“下游阶段”,但只要上游阶段发生异常(包括 supplyAsync 抛出异常、或前一个 thenApply 中抛出)、或上游被取消(cancel(true)),后续的 thenApply/thenAccept 就完全跳过,不会进回调,也不会报错。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 链式调用中,每个阶段都要考虑异常分支:用
exceptionally()或handle()接住错误,否则整条链静默中断 -
thenAccept()参数是void,适合纯消费;需要返回新值就用thenApply(),别混用导致类型推导失败 - 如果上游是
CompletableFuture<void></void>(比如runAsync()),只能接thenRun()或thenAccept(),不能用thenApply()
CompletableFuture.allOf() 为什么拿不到所有结果
allOf() 返回的是 CompletableFuture<void></void>,它只表示“全部完成”,不聚合结果。很多人误以为能直接 get 出一个 List,结果得到 null 或 ClassCastException。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 要收集结果,得手动 map:把每个
CompletableFuture<t></t>转成CompletableFuture<object></object>,再用allOf()+join()拿数组,最后逐个get() - 更稳妥的做法是用
Stream<completablefuture>></completablefuture>+collect(Collectors.collectingAndThen(...)),但注意别在流里直接get(),会阻塞 -
allOf()遇到任一 Future 异常就整体失败,不会等全部结束 —— 如果需要“尽力执行”,得用handle()包裹每个子 Future
异步链里最麻烦的从来不是写法,而是异常传播路径和线程池归属——这两处漏掉,日志里连堆栈都看不到。











