admin管理员组

文章数量:1441190

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对象

本文标签: Runnable与Callable比较