SWE · Phone Concurrency

Apple Java 并发:Runnable 与 Callable 有什么区别

比较任务返回值、受检异常、ExecutorService 提交、Future 结果与取消语义。

题目重点:比较任务返回值、受检异常、ExecutorService 提交、Future 结果与取消语义。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 按标准 Java `java.lang.Runnable`、`java.util.concurrent.Callable<V>` 与 `ExecutorService` 解释;题目未要求特定 JDK 新特性。
  • 用后台计算并获取结果的训练场景说明两者,不把任务接口本身等同线程或执行器。

解题路线

  1. `Runnable.run()` 返回 void,方法签名不声明受检异常;`Callable<V>.call()` 返回 V 且可声明 `throws Exception`。选择依据是任务是否要把值或失败通过统一异步结果交给调用者。
  2. `ExecutorService.submit(Callable<V>)` 得到 `Future<V>`;提交 Runnable 也可得 `Future<?>`,成功结果通常为 null,或用带给定 result 的重载/FutureTask 包装。不能说 Runnable 完全不能与 Future 配合。
  3. `Future.get()` 可能阻塞;任务异常会作为执行异常传给取结果方,调用方还须处理等待中断、取消与超时。`cancel(true)` 尝试中断执行,不保证代码立刻终止。
  4. 线程池与任务接口分开:一个 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 想返回固定值有哪些包装方式?

技术参考

✓已复制微信号!