Future 与 Callable
候选人小郑在面试字节 P6 时,面试官问道:
"Callable 和 Runnable 有什么区别?Future 是什么?"
小郑说:"Callable 有返回值..."面试官追问:"Future 的 get() 是怎么阻塞的?"
小郑答不上来。面试官继续:"FutureTask 是什么?它和线程池的关系是什么?"
小郑彻底卡住了...
一、核心问题:Future 与 Callable 🔴
1.1 问题拆解
1.2 ❌ 错误示范
候选人原话 A:"Callable 和 Runnable 差不多,都是线程执行的东西。"
问题诊断:Callable 可以返回结果、可以抛出受检异常;Runnable 不能返回结果(JDK 1.4 之前),也不能抛出受检异常(run() 签名没有 throws)。
候选人原话 B:"Future.get() 是 busy-waiting(忙等待)。"
问题诊断:Future.get() 使用 LockSupport.park() 挂起线程,不消耗 CPU。这比忙等待高效得多。
1.3 标准回答
P5 级别:Callable vs Runnable
Runnable 的局限:
Callable 的设计:
Future 的作用:
Callable 的 call() 是异步执行的,Future 用于获取异步结果:
P6 级别:Future 的阻塞机制
FutureTask 的状态机:
状态转换图:
get() 的阻塞实现:
get(timeout) 的超时版本:
P7 级别:FutureTask 与线程池
FutureTask 的设计:
FutureTask 同时实现了 Runnable 和 Future 接口:
这使得 FutureTask 可以:
- 被 Thread 直接运行:
new Thread(new FutureTask<>(callable)).start()- 被 ExecutorService.submit() 返回:
Future<V> future = executor.submit(callable)线程池的 submit() 流程:
【面试官心理】 这道题我能问到 P7 级别,是因为 FutureTask 的状态机是 JUC 中的经典设计。能说清 COMPLETING 中间状态的候选人说明他理解了这个设计的复杂性。能说出 RunnableFuture 双重身份的候选人说明他对接口设计有了解。
1.4 追问升级
追问 1:FutureTask 的 DONE 和 COMPLETING 有什么区别?
- COMPLETING:正在设置结果(set() 正在执行中,还没设置完)
- DONE:已完成(结果已设置或异常/取消)
COMPLETING 是一个极短的中间状态,发生在结果赋值的一瞬间。在 awaitDone() 中需要处理这个状态以避免遗漏。
追问 2:Future 的 cancel() 做了什么?
如果
mayInterruptIfRunning=true,会中断正在执行的线程;如果为 false,则设置状态为 CANCELLED,但不会中断线程(任务继续运行,但 get() 会抛 CancellationException)。
二、与 CompletableFuture 的对比 🟡
2.1 Future 的局限性
2.2 CompletableFuture 的优势
三、生产避坑 🟡
3.1 Future.get() 忘记超时
3.2 异常处理
面试加分点:能说出"JDK 8 的 CompletableFuture 内部使用 ForkJoinPool.commonPool(),可以通过 completableFuture.getNumberOfDependents() 查看依赖该 future 的任务数",说明他对 JDK 8 异步工具有深入了解。
面试陷阱:被问到"FutureTask 的 runner 是什么",很多人会说"是 Callable 的返回值"。准确答案是:runner 是持有 FutureTask 并在其中执行 call() 的 Thread 对象,通过 CAS 设置(UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))。