callable 与 runnable 的核心区别在于 callable 的 call() 方法有返回值且可抛受检异常,而 runnable 的 run() 方法无返回值、不可抛受检异常;必须通过 executorservice.submit() 获取 future 才能获取结果并处理异常。

Callable 接口和 Runnable 的核心区别在哪
Callable 和 Runnable 都用于定义线程任务,但关键差异在于返回值和异常处理:Callable 的 call() 方法能返回任意类型(通过泛型指定),且允许抛出受检异常;Runnable 的 run() 方法无返回值、不能抛出受检异常。
这意味着:如果你需要线程执行后拿到结果(比如计算总和、查询数据库响应、解析 JSON),必须用 Callable,而不是试图在 Runnable 里塞个 return——那根本编译不过。
常见错误现象:Cannot resolve method 'submit(MyRunnable)' 或 incompatible types: MyRunnable cannot be converted to Callable,往往是因为误把 Runnable 实例传给了只接受 Callable 的 ExecutorService.submit() 重载方法。
怎么用 ExecutorService 提交并获取 Callable 的返回值
ExecutorService 的 submit() 方法接收 Callable 并返回 Future,这才是取结果的唯一可靠路径:
- 别直接 new Thread(new MyCallable()).start() —— 这样你拿不到
Future,也就无法 get() 结果
- 必须用
executor.submit(new MyCallable()),返回值是 Future<t></t>
-
Future.get() 是阻塞调用,会一直等到任务完成;如果想避免卡死,用 get(long timeout, TimeUnit unit) 并捕获 TimeoutException
- 任务抛出的异常不会在
call() 里直接打印,而是被包装进 ExecutionException,需在 get() 调用时捕获
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
try {
Integer result = future.get(); // 阻塞等待
System.out.println(result); // 输出 42
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // 真正的异常,比如 RuntimeException
}
executor.shutdown();
</integer>
为什么不能直接 new Callable()?得配合 Future 使用
Callable 是函数式接口,但它的 call() 方法不能像普通方法那样被“主动调用”——它必须由线程池或 ForkJoinPool 等执行器调度。你写 new Callable<string>() { ... }.call()</string> 是在当前线程同步执行,完全没走多线程,还失去了超时控制、取消能力等 Future 提供的关键特性。
Future,也就无法 get() 结果executor.submit(new MyCallable()),返回值是 Future<t></t>
Future.get() 是阻塞调用,会一直等到任务完成;如果想避免卡死,用 get(long timeout, TimeUnit unit) 并捕获 TimeoutException
call() 里直接打印,而是被包装进 ExecutionException,需在 get() 调用时捕获Callable 是函数式接口,但它的 call() 方法不能像普通方法那样被“主动调用”——它必须由线程池或 ForkJoinPool 等执行器调度。你写 new Callable<string>() { ... }.call()</string> 是在当前线程同步执行,完全没走多线程,还失去了超时控制、取消能力等 Future 提供的关键特性。
典型误区:
- 把
Callable当作普通工具类,手动调用call()—— 失去并发意义 - 忽略
Future.isDone()或Future.cancel(true),导致无法感知任务状态或及时中断长耗时任务 - 提交多个
Callable后,用 for 循环逐个get(),造成串行等待;应先全部 submit,再统一 get(或用invokeAll())
实际开发中容易漏掉的三件事
真实场景下,光让代码跑起来远远不够:
-
ExecutorService必须显式shutdown()或shutdownNow(),否则 JVM 不会退出(线程池默认使用非守护线程) - 如果任务可能长时间阻塞(如网络请求),务必设置
get(timeout, unit),否则主线程可能永久挂起 -
Future.get()抛出的ExecutionException是包装器,真正业务异常藏在e.getCause()里,日志里不 unpack 就很难定位问题
Callable 写得再漂亮,上线后也容易出 silent failure。










