Callable能返回值且可抛受检异常,Runnable不能:Callable的call()有泛型返回值并支持throws声明,需通过Future.get()获取结果并处理ExecutionException;Runnable的run()返回void、不支持受检异常,提交后Future.get()恒返回null。

Callable 能返回值,Runnable 不能
这是最直接的区别:Callable 的 call() 方法有返回值(类型由泛型指定),而 Runnable 的 run() 方法返回 void。如果你需要线程执行完后拿到结果(比如计算结果、查询响应、状态码),必须用 Callable。
常见错误现象:试图在 Runnable 里写 return "done" —— 编译直接报错,因为方法签名不允许返回值。
-
Callable的call()可以返回"success" -
Runnable的run()只能执行逻辑,结果得靠外部变量、回调或共享容器“塞出去” - 返回值不是立刻拿到的,要通过
Future.get()获取,这会阻塞当前线程(除非用isDone()或超时重试)
Callable 声明受检异常,Runnable 不行
call() 方法允许抛出受检异常(Exception 及其子类),而 run() 方法签名禁止这么做——它只能抛运行时异常(RuntimeException)或错误(Error)。
使用场景:比如线程内做文件读取、HTTP 请求,这些操作天然抛 IOException 或 InterruptedException。用 Runnable 就得包一层 try-catch 吞掉或转成 RuntimeException;用 Callable 可以原样往上抛,由调用方决定怎么处理。
立即学习“Java免费学习笔记(深入)”;
- 写
new Callable是合法的() { public Integer call() throws IOException { ... } } - 同理写
throws InterruptedException也没问题 - 但
Runnable的run()方法体里写throw new IOException()会编译失败
提交到线程池后,返回类型不同
用 ExecutorService 提交任务时,返回对象类型决定了你能否获取结果:
-
executor.submit(new Runnable() {...})→ 返回Future>,但get()总是返回null -
executor.submit(new Callable→ 返回() {...}) Future,get()返回实际结果 - 注意:即使传入
Runnable+result(即submit(Runnable, T)重载),也是把那个result当作“默认返回值”,不是线程执行产生的
性能影响:Future.get() 是同步阻塞调用,如果没设置超时且任务卡住,调用线程就一直挂起。别在主线程或 I/O 密集路径上无脑 get()。
为什么不能直接用 Thread 启动 Callable
Thread 构造器只接受 Runnable,不支持 Callable。想让 Callable 跑起来,必须借助 FutureTask 包装——它既是 Runnable(可被 Thread 执行),又实现了 Future(提供 get())。
容易踩的坑:
- 写
new Thread(new Callable<...>() {...})→ 编译失败 - 正确做法:
FutureTasktask = new FutureTask(callable); new Thread(task).start(); String result = task.get(); - 重复调用
task.get()没问题,但第一次之后的结果是缓存的;若第一次调用时任务抛异常,后续get()会重新抛出包装后的ExecutionException
真正复杂的地方不在接口定义,而在结果获取时机和异常传播链——Callable 抛的异常会被包进 ExecutionException,需要用 getCause() 解包才能看到原始异常。这点常被忽略。








