future.get() 是阻塞调用,使当前线程挂起直至任务完成,需注意异常处理、超时控制、资源泄漏及勿在ui主线程使用。

Future.get 会一直卡住主线程直到任务完成
Future.get() 是阻塞调用,调用后当前线程会挂起,不消耗 CPU,但也不会继续往下走,直到对应任务结束(正常返回或抛出异常)。这不是“轮询”,也不是“忙等待”,底层靠的是 LockSupport.park() 或类似机制实现的线程暂停。
常见错误现象:Future.get() 调用后程序停住不动,日志断在那,IDE 调试时线程状态显示为 WAITING 或 TIMED_WAITING;排查时容易误以为是死锁或线程池没启动。
- 务必确认任务已提交到线程池(比如通过
executor.submit()),否则Future对象可能根本没绑定执行逻辑 - 如果任务本身有死循环、IO 阻塞未设超时、或依赖另一个也卡住的 Future,
get()就真的会无限等下去 - 不要在 Swing/AWT 的 Event Dispatch Thread 或 Android 主线程里直接调用
get(),会导致界面冻结
必须用 try-catch 包裹,否则编译不通过
Future.get() 声明抛出两个受检异常:InterruptedException 和 ExecutionException。Java 编译器强制要求处理,漏掉任一都会报错。
InterruptedException 表示当前线程在等待时被其他线程调用了 interrupt();ExecutionException 则是任务内部抛出的异常的包装,其 getCause() 才是原始异常。
- 捕获
InterruptedException后建议恢复中断状态:Thread.currentThread().interrupt(); - 不要只打印
ExecutionException,要展开e.getCause()才能看到业务代码里真正出错的地方 - 若确定不会中断、也不关心任务异常细节,可用
Uninterruptibles.getUninterruptibly(future)(Guava)简化,但它会吞掉中断信号
带超时的 get(long, TimeUnit) 更安全,但 timeout 本身也有陷阱
用 future.get(5, TimeUnit.SECONDS) 可避免无限等待,但要注意:超时触发后,任务**并不会自动取消**,它仍在后台线程中运行,只是你不再等了。
典型问题场景:HTTP 请求 Future 超时返回,但连接其实还在读响应体;数据库查询 Future 超时,但 SQL 仍在数据库里执行——这些都可能造成资源泄漏或重复提交。
- 超时后应主动调用
future.cancel(true)尝试中断任务线程(前提是任务逻辑响应中断) -
cancel(true)对正在 sleep、wait、join 或 NIO 阻塞的操作有效,对纯计算循环无效,除非手动检查Thread.interrupted() - 注意
TimeUnit.MILLISECONDS和TimeUnit.SECONDS混用导致实际等待时间差 1000 倍
CompletableFuture 提供非阻塞替代方案,但 get() 语义不变
虽然 CompletableFuture 支持 thenApply、whenComplete 等异步链式操作,但它的 get() 方法行为和原始 Future 完全一致——仍是阻塞的。
有人误以为换成 CompletableFuture 就能“自动非阻塞”,其实不然。真正解耦的写法是用回调,而不是换一个 Future 实现。
-
CompletableFuture.supplyAsync(() -> heavyWork()).thenAccept(System.out::println)才是不阻塞主线程的正确姿势 - 如果必须同步取值(比如老接口契约不能改),用
CompletableFuture的get()并没有收益,反而增加对象创建开销 -
CompletableFuture的getNow(T)只在已完成时返回结果,未完成就返回默认值,不阻塞,但要注意竞态:刚判断完未完成,下一毫秒它就完成了,你却拿不到这个结果
最易被忽略的一点:Future.get() 的阻塞是线程粒度的,不是方法粒度的。哪怕你在 Spring @Async 方法里调用了它,该线程依然会卡住——这跟“异步注解”本身无关,别被注解名误导。











