1. 概述
从Java早期开始,多线程就是语言的核心特性。Runnable
是表示多线程任务的基础接口,而Java 1.5引入的Callable
则是Runnable
的改进版。本文将深入探讨两者的区别及应用场景。
2. 执行机制
两个接口都设计用于表示可被多线程执行的任务:
- ✅
Runnable
可通过Thread
类或ExecutorService
执行 - ❌
Callable
只能通过ExecutorService
执行 - ⚠️ 直接用
Thread
启动Callable
会编译报错
3. 返回值处理
3.1 Runnable方案
Runnable
是函数式接口,其run()
方法无参数无返回值:
public interface Runnable {
public void run();
}
适用于不需要返回结果的场景,比如事件日志记录:
public class EventLoggingTask implements Runnable {
private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class);
@Override
public void run() {
logger.info("Message");
}
}
通过ExecutorService
启动任务:
public void executeTask() {
executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new EventLoggingTask());
executorService.shutdown();
}
此时Future
对象不会持有任何值。
3.2 Callable方案
Callable
是泛型接口,其call()
方法返回泛型值V
:
public interface Callable<V> {
V call() throws Exception;
}
以计算阶乘为例:
public class FactorialTask implements Callable<Integer> {
int number;
// 标准构造器
public Integer call() throws InvalidParamaterException {
int fact = 1;
// ...
for(int count = number; count > 1; count--) {
fact = fact * count;
}
return fact;
}
}
结果通过Future
对象获取:
@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
FactorialTask task = new FactorialTask(5);
Future<Integer> future = executorService.submit(task);
assertEquals(120, future.get().intValue());
}
4. 异常处理
4.1 Runnable的局限
由于run()
方法签名没有throws
子句:
- ❌ 无法传播受检异常
- ⚠️ 只能通过try-catch内部处理
- 常见踩坑:未捕获异常会导致线程静默终止
4.2 Callable的优势
call()
方法包含throws Exception
子句,可轻松传播受检异常:
public class FactorialTask implements Callable<Integer> {
// ...
public Integer call() throws InvalidParamaterException {
if(number < 0) {
throw new InvalidParamaterException("Number should be positive");
}
// ...
}
}
通过ExecutorService
执行时,异常会被收集到Future
对象:
- 调用
Future.get()
会抛出ExecutionException
(包装原始异常) - 不调用
get()
则异常不会报告,任务仍标记为完成
@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
Integer result = future.get().intValue();
}
@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
assertEquals(false, future.isDone());
}
5. 总结
特性 | Runnable | Callable |
---|---|---|
返回值 | ❌ 无 | ✅ 有(通过Future获取) |
异常处理 | ❌ 无法传播受检异常 | ✅ 支持异常传播 |
执行方式 | ✅ Thread/ExecutorService | ❌ 仅ExecutorService |
Java版本 | 1.0+ | 1.5+ |
简单粗暴的选择建议:
- 需要返回结果或处理异常 → 用
Callable
- 简单异步任务 → 用
Runnable
完整代码示例请查看GitHub仓库。