Runnable与Callable比较
1. 概述
从Java的早期开始,多线程一直是该语言的一个主要方面。Runnable是为表示多线程任务而提供的核心接口,Java 1.5 提供了Callable作为Runnable 的改进版本。
在本教程中,我们将探讨这两个接口的差异和应用。
2. 执行机制
这两个接口都旨在表示可由多个线程运行的任务。我们可以使用Thread类或ExecutorService 运行Runnable任务,而我们只能使用后者运行Callable。
3. 返回值
让我们更深入地了解这些接口如何处理返回值。
3.1. 使用Runnable
Runnable接口是一个函数接口,具有单个run() 方法,该方法不接受任何参数或返回任何值。
这适用于我们不查找线程执行结果的情况,例如传入事件日志记录:
代码语言:javascript代码运行次数:0运行复制public interface Runnable {
public void run();
}
代码语言:javascript代码运行次数:0运行复制让我们通过一个例子来理解这一点:
代码语言:javascript代码运行次数:0运行复制public class EventLoggingTask implements Runnable{
private Logger logger
= LoggerFactory.getLogger(EventLoggingTask.class);
@Override
public void run() {
logger.info("Message");
}
}
代码语言:javascript代码运行次数:0运行复制在此示例中,线程将仅从队列中读取消息并将其记录在日志文件中。任务不返回任何值。
我们可以使用executorService启动任务:
代码语言:javascript代码运行次数:0运行复制public void executeTask() {
executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new EventLoggingTask());
executorService.shutdown();
}
在这种情况下,Future对象将不保存任何值。
3.2.使用Callable
Callable接口是一个泛型接口,其中包含返回泛型值V 的单个call() 方法:
代码语言:javascript代码运行次数:0运行复制public interface Callable<V> {
V call() throws Exception;
}
让我们看一下计算一个数字的阶乘:
代码语言:javascript代码运行次数:0运行复制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对象中返回:
代码语言:javascript代码运行次数:0运行复制@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.使用Callable
Callable 的call() 方法包含 “throwsException” 子句,因此我们可以轻松地进一步传播已检查的异常:
代码语言:javascript代码运行次数:0运行复制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,它包装了原始异常:
代码语言:javascript代码运行次数:0运行复制@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
Integer result = future.get().intValue();
}
代码语言:javascript代码运行次数:0运行复制
在上面的测试中,抛出ExecutionException,因为我们传递了一个无效的数字。我们可以在这个异常对象上调用getCause() 方法来获取原始检查的异常。
如果我们不调用Future类的 get()方法,则call()方法抛出的异常将不会报告回来,并且任务仍将标记为已完成:
代码语言:javascript代码运行次数:0运行复制@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
assertEquals(false, future.isDone());
}
即使我们已将参数负值的异常抛出到FactorialCallableTask,上述测试也会成功通过。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2023-02-16,如有侵权请联系 cloudcommunity@tencent 删除接口异常javarunnable对象
发布评论