futuretask 本身不阻塞,但 get() 会阻塞线程;必须带超时调用(如 get(3, seconds)),超时值按p99+余量设定,捕获 timeoutexception 后调 cancel(true);不在 tomcat 线程中 submit,应提交至独立线程池并设 callerrunspolicy;复杂流程改用 completablefuture 链式编排;轮询 isdone() 仅限低频非关键路径。

FutureTask 本身不阻塞,但调用 get() 会阻塞当前线程——这是绝大多数阻塞问题的根源。关键不是不用 FutureTask,而是控制它何时、如何交出结果。
必须带超时调用 get()
永远不要写 future.get()。所有 get() 调用都应指定超时:
- 用
future.get(3, TimeUnit.SECONDS)替代无参get() - 超时值按下游依赖的 P99 延迟 + 重试余量设定:IO 类任务建议 3–10 秒,计算类可设为 2–5 秒
- 捕获
TimeoutException后立即调用future.cancel(true),主动中断执行线程
不在 Tomcat 或请求线程中直接 submit
Servlet 容器线程(如 Tomcat worker)是宝贵资源,不适合承载可能长时间运行或阻塞的任务:
- 将 FutureTask 提交到独立业务线程池,例如
newFixedThreadPool(8)或newCachedThreadPool() - 避免用
Executors.newSingleThreadExecutor(),它易成单点瓶颈 - 线程池拒绝策略推荐
CallerRunsPolicy,让调用方自己执行,既防丢任务,也便于压测暴露容量问题
改用 CompletableFuture 链式编排
FutureTask 是较底层的封装,适合简单异步;复杂流程建议升级为 CompletableFuture:
- 用
supplyAsync(..., executor).thenApply(...)替代手动 new FutureTask + submit - 多个异步任务并行时,用
allOf()或anyOf()统一等待,避免逐个get()拉长总耗时 - 需串行依赖时,用
thenCompose()实现非阻塞接力,而非等前一个get()完再提交下一个
轮询 isDone() 仅限低频/非关键路径
轮询虽不阻塞,但消耗 CPU 且响应滞后,只适用于对实时性要求不高、且无法加回调的场景:
- 检查
future.isDone(),为真再调get()(此时不会阻塞) - 轮询间隔不宜过短(如 100ms),避免空转;可配合
Thread.sleep()或LockSupport.parkNanos() - 非核心逻辑中可设短超时(如 200ms),快速失败降级,不拖慢主链路
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











