介绍java 中 Runnable 和 Callable
从java早期开始,多线程已经就是其主要特性之一。Runable接口是表现多线程任务核心接口,Callable是java1.5之后引入的新街口。
本文,我们探讨下这两个接口之间的差别。
## 执行机制
这两个接口都代表能被多线程执行的任务,Runable任务可以使用Thread和ExecutorService执行,而Callable只能使用后者执行。
返回值
让我们深入探讨这些接口处理的返回值。
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对象不包含任何值。
Callable
Callable接口是一个通用接口,包含单个call方法————其返回泛型类型V:
public interface Callable<V> {
V call() throws Exception;
}
看一个计算斐波那契数列示例:
public class FactorialTask implements Callable<Integer> {
int number;
// standard constructors
public Integer call() throws InvalidParamaterException {
int fact = 1;
// ...
for(int count = number; count > 1; count--) {
fact = fact * count;
}
return fact;
}
}
call方法返回值是Future对象:
@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
FactorialTask task = new FactorialTask(5);
Future<Integer> future = executorService.submit(task);
assertEquals(120, future.get().intValue());
}
异常处理
下面我们看如何处理任务执行异常情况:
使用Runnable
因为其run方法没有任何throws子句作为方法签名规范,无法进一步传播检查异常。
使用Callable
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执行Callable时,异常被收集在Future对象中,当调用Future.get()方法时可以检查到。其返回ExecutionException————其包装了原始异常:
@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
Integer result = future.get().intValue();
}
在上面的测试中,我们传递一个无效数值,会跑出ExecutionException异常。可以通过调用其getCause方法获得其原始检查异常。如果我们调用Future类的get方法,那么call方法抛出的异常不会被检测到,执行任务仍然被标记为已执行完成:
@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
assertEquals(false, future.isDone());
}
上面测试代码会测试通过,因为传递负值参数给FactorialCallableTask会抛出异常。
总结
本文,我们探讨了Runnable 和 Callable 接口之间的差异,尤其是异常处理在实际项目中非常有用。