原文地址:https://www.baeldung.com/java-runnable-callable
翻译可以不准确,以原文为准
1. 概述
自 Java 早期开始,多线程一直是该语言的一个重要特性。Runnable 是代表提供多线程任务的核心接口,Java 1.5 提供了 Callable 作为 Runnable 的改进版本。
在本教程中,我们将探讨这两个接口的差异和应用。
2、执行机制
这两个接口都旨在表示可由多个线程运行的任务。我们可以使用 Thread 类或 ExecutorService 来运行 Runnable 任务,而我们只能使用后者来运行 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 接口是一个泛型接口,包含一个返回泛型值 V 的 call() 方法:
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());
}
4.异常处理
让我们看看它们是否适合异常处理。
4.1 Runnable
由于方法签名没有指定“throws”子句,因此我们没有办法进一步传播受检查的异常。
4.2 Runnable
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() {
FactorialTask task = new FactorialTask(-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 参数为负值抛出异常,上述测试也会成功通过。
5. 结论
在本文中,我们探讨了 Runnable 和 Callable 接口之间的差异。
与往常一样,本文的完整代码可以在 GitHub 上获取。