[面试题] Callable、Future、FutureTask 之间是什么关系?
📖 版块 A:知识讲解版 (温故知新)
-
Callable:
- 表示一个有返回值、可抛异常的异步任务。
- 核心方法是
call()。
-
Future:
- 表示一个异步结果占位符。
- 可以查询任务是否完成、是否取消,以及通过
get()获取结果。
-
FutureTask:
- 是一个把
Callable和Future连接起来的实现类。 - 它既实现了
Runnable,又实现了Future。 - 因此既可以丢给线程执行,也可以在未来拿结果。
- 是一个把
-
三者关系:
Callable负责描述“要做什么”。Future负责描述“结果怎么拿”。FutureTask负责把“任务”和“结果”包装成一个可执行对象。
-
使用方式:
ExecutorService.submit(Callable)会返回Future。- 手动创建
FutureTask后,既可以new Thread(futureTask).start(),也可以交给线程池执行。
-
局限性:
Future#get()通常是阻塞的。- 多任务编排能力弱,回调和异常链路不够优雅。
🎤 版块 B:面试实战版 (高手逐字稿)
Q:Callable、Future、FutureTask 之间是什么关系?
💬 高手逐字稿:
这三个类其实是在解决同一件事的不同环节,也就是 Java 里最基础的一套异步编程模型。
先看 Callable,它描述的是一个“可返回结果的任务”。和 Runnable 最大的区别有两个:第一,它有返回值;第二,它允许抛受检异常。所以凡是任务执行完之后,我还想拿到计算结果,或者我不想把异常吞掉,都会更倾向用 Callable。
然后是 Future。它并不负责执行任务,而是表示一个“未来会完成的结果”。你可以把它理解成一个异步占位符。线程池把任务扔出去之后,先给你一个 Future,你后面可以通过 isDone() 看任务结束没有,通过 cancel() 取消任务,通过 get() 阻塞等待结果。
真正把这两者连起来的是 FutureTask。它既实现了 Runnable,说明它可以被线程或者线程池执行;又实现了 Future,说明它本身又能持有执行结果。所以 FutureTask 本质上就是一个“可执行的异步结果容器”。我们把 Callable 包进 FutureTask 之后,这个对象就既知道“怎么执行任务”,也知道“怎么保存和返回结果”。
如果从链路上讲,就是:
Callable定义任务逻辑;FutureTask包装任务并承接结果;Future暴露统一的结果获取能力。
但这套模型也有明显局限。最大的问题就是 Future#get() 很容易把异步重新写回同步,因为一调用就阻塞了。第二,它不擅长做多个异步任务之间的组合、回调和异常传播。所以在现代业务代码里,简单场景我会用 Future,但只要涉及任务编排,通常会切到 CompletableFuture。
最后更新于: