callable 与 runnable 的核心区别在于:callable 的 call() 方法有返回值且可抛异常,而 runnable 的 run() 方法无返回值、不能抛受检异常;callable 必须通过 executorservice 提交并用 future 获取结果,不可直接用于 thread 构造。

Callable 和 Runnable 的核心区别在哪
Runnable 的 run() 方法没有返回值、也不能抛出受检异常;Callable 的 call() 方法必须返回一个值(泛型类型),且可以抛出任意异常。这是选择 Callable 的根本原因——你明确需要任务执行后的结果,比如计算结果、查询响应、状态码等。
注意:Callable 本身不能直接交给线程运行,必须配合 ExecutorService 使用,最终通过 Future 获取结果。
如何提交 Callable 并安全获取返回值
调用 executor.submit() 提交 Callable 后会立即返回一个 Future 对象。这个对象是“未来结果”的占位符,不是实际值。
-
future.get()是阻塞操作,会一直等到任务完成才返回结果;若任务抛异常,会包装成ExecutionException抛出 -
future.get(3, TimeUnit.SECONDS)可设超时,超时后抛TimeoutException,避免无限等待 - 务必在
try-catch中处理InterruptedException和ExecutionException,否则可能掩盖关键错误
示例:
立即学习“Java免费学习笔记(深入)”;
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
try {
Integer result = future.get(2, TimeUnit.SECONDS); // 带超时
System.out.println(result); // 输出 42
} catch (TimeoutException e) {
System.err.println("任务超时");
} catch (ExecutionException e) {
System.err.println("任务执行异常:" + e.getCause());
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
}
为什么不能直接 new Thread(new Callable(...))
Thread 构造函数只接受 Runnable,不接受 Callable。强行包装(如用匿名 Runnable 调用 call())会丢失返回值和异常传播能力,等于白用 Callable。
常见误写:
// ❌ 错误:无法编译,构造函数不匹配
new Thread(new Callable<String>() { ... });
正确路径只有一条:走 ExecutorService.submit(Callable) → 得 Future → 调 get()。
Callable 在线程池关闭时的行为风险
如果在 ExecutorService.shutdown() 后还调用 submit(),会抛 RejectedExecutionException;如果任务已提交但尚未执行,shutdown() 不会取消它,仍可能执行并产生结果。
- 要用
shutdownNow()尝试中断正在运行的任务,但前提是任务本身响应中断(检查Thread.interrupted()或捕获InterruptedException) -
future.cancel(true)可单独取消某个任务,但对已进入finally或 CPU 密集型无中断点的逻辑无效 - 务必在 finally 块中调用
executor.shutdown(),否则 JVM 可能不退出
真正容易被忽略的是:Callable 里的耗时 IO 或循环,若没主动检查中断状态,cancel(true) 和 shutdownNow() 都不会让它停下来。










