题目重点:比较任务返回值、受检异常、ExecutorService 提交、Future 结果与取消语义。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 按标准 Java `java.lang.Runnable`、`java.util.concurrent.Callable<V>` 与 `ExecutorService` 解释;题目未要求特定 JDK 新特性。
- 用后台计算并获取结果的训练场景说明两者,不把任务接口本身等同线程或执行器。
解题路线
- `Runnable.run()` 返回 void,方法签名不声明受检异常;`Callable<V>.call()` 返回 V 且可声明 `throws Exception`。选择依据是任务是否要把值或失败通过统一异步结果交给调用者。
- `ExecutorService.submit(Callable<V>)` 得到 `Future<V>`;提交 Runnable 也可得 `Future<?>`,成功结果通常为 null,或用带给定 result 的重载/FutureTask 包装。不能说 Runnable 完全不能与 Future 配合。
- `Future.get()` 可能阻塞;任务异常会作为执行异常传给取结果方,调用方还须处理等待中断、取消与超时。`cancel(true)` 尝试中断执行,不保证代码立刻终止。
- 线程池与任务接口分开:一个 Callable 可以在执行器中运行;Lambda 选择目标函数式接口时要结合返回值和异常签名。生产场景还要设超时、关闭执行器和背压。
手动走一遍
只需异步记录一条可重试事件可用 Runnable;需计算图像 checksum 并交给调用方比较,可用 Callable<String>,提交后 Future.get(timeout) 取值。若任务抛 IOException,Callable 可声明并由执行结果承载;Runnable 需要自行处理或包装。
规模与取舍
两个接口只描述任务入口,没有固定时间复杂度。总耗时由任务工作量、排队、线程数与阻塞决定;`get()` 最坏可一直等待,除非给 timeout 或取消策略。
易错点与追问
容易失分的边界
- 说 Runnable 不能被 submit/不能返回 Future。
- 把 Callable 当作自动创建新线程。
- 认为 cancel(true) 强制杀死任务。
- 在请求主线程无期限 `Future.get()` 导致线程池饥饿。
- 只背返回值差异,不提异常传播与执行器生命周期。
继续深挖
- `execute` 与 `submit` 处理异常的可观察方式有什么不同?
- Future.get 超时后任务是否自动停止?
- 大量阻塞任务怎样决定线程池/虚拟线程与限流策略?
- 一个 Runnable 想返回固定值有哪些包装方式?
技术参考
- Oracle Callable API:接口返回值与异常定义。
- Oracle FutureTask API:Runnable/Callable 都可包装为 FutureTask。
