Java 中 Runnable 和 Callable 区别(翻译)

原文地址: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 上获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值