FutureTask 抛出的异常被封装在内部状态中,调用 get() 时才以 ExecutionException 形式抛出,需通过 getCause() 获取原始异常;若不调用 get(),异常将被静默持有。

FutureTask 在执行过程中抛出的异常不会直接在子线程中打印或中断主线程,而是被**捕获并封装进 FutureTask 内部状态**,直到调用 get() 方法时才抛出 —— 且是以 ExecutionException 包装的形式暴露出来,其 getCause() 才是原始异常。
异常被捕获并延迟抛出
FutureTask 实现了 Runnable 和 Future 接口,其 run() 方法内部会调用传入的 Callable 的 call()。如果 call() 抛出异常,FutureTask 不会向外传播,而是:
- 将异常对象保存在内部成员变量(如
outcome字段)中; - 将任务状态设为
EXCEPTIONAL; - 后续调用
get()时,检查到该状态,就构造ExecutionException并抛出。
正确获取原始异常的方法
不能直接 catch RuntimeException 或业务异常,必须通过 ExecutionException 层层解包:
try {
result = futureTask.get(); // 此处才真正抛出异常
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // 获取原始异常
if (cause instanceof NullPointerException) {
// 处理空指针
} else if (cause instanceof MyBusinessException) {
// 处理自定义异常
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
未调用 get() 就可能“丢失”异常
这是最容易忽略的问题:如果任务执行出错,但你从未调用 get() 或 isDone() 后忽略结果,异常将一直被静默持有,不会触发任何日志或告警。
立即学习“Java免费学习笔记(深入)”;
- 建议:只要提交了 FutureTask,就应在合理位置调用
get()(或带超时的get(long, TimeUnit)); - 若仅关心执行完成(不关心结果),也应至少调用
isDone()+get()检查异常,或使用FutureTask#done()钩子方法重写处理逻辑; - 在线程池中使用时,异常不会导致线程销毁,但会影响任务可观测性。
利用 done() 方法提前响应异常
FutureTask 提供了受保护的 done() 方法,它在线程执行结束(无论成功或异常)后自动被调用,适合做统一收尾或异常记录:
FutureTask<String> task = new FutureTask<>(new Callable<String>() {
public String call() throws Exception {
throw new IllegalArgumentException("模拟错误");
}
}) {
protected void done() {
try {
get(); // 此处会触发 ExecutionException
} catch (ExecutionException e) {
System.err.println("任务失败:" + e.getCause());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
不复杂但容易忽略:异常不在发生时浮现,而藏在 get() 里等你主动打开。


















