Runnable vs. Callable in Java – Java中的Runnable与Callable

最后修改: 2017年 8月 31日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Since Java’s early days, multithreading has been a major aspect of the language. Runnable is the core interface provided for representing multithreaded tasks, and Java 1.5 provided Callable as an improved version of Runnable.

从Java的早期开始,多线程就一直是该语言的一个主要方面。Runnable是为表示多线程任务而提供的核心接口,Java 1.5提供了Callable作为Runnable的改进版本。

In this tutorial, we’ll explore the differences and the applications of both interfaces.

在本教程中,我们将探讨这两种界面的区别和应用。

2. Execution Mechanism

2.执行机制

Both interfaces are designed to represent a task that can be run by multiple threads. We can run Runnable tasks using the Thread class or ExecutorService, whereas we can only run Callables using the latter.

这两个接口都是为了表示一个可以被多个线程运行的任务。我们可以使用Thread类或ExecutorService来运行Runnable任务,而我们只能使用后者来运行Callables。

3. Return Values

3.返回值

Let’s look deeper at how these interfaces handle return values.

让我们深入了解一下这些接口是如何处理返回值的。

3.1. With Runnable

3.1.使用Runnable

The Runnable interface is a functional interface and has a single run() method that doesn’t accept any parameters or return any values.

Runnable接口是一个功能接口,它有一个单一的run()方法,不接受任何参数或返回任何值。

This works for situations where we aren’t looking for a result of the thread execution, such as incoming events logging:

这适用于我们不需要寻找线程执行结果的情况,例如传入事件的记录。

public interface Runnable {
    public void run();
}

Let’s understand this with an example:

让我们通过一个例子来理解这一点。

public class EventLoggingTask implements  Runnable{
    private Logger logger
      = LoggerFactory.getLogger(EventLoggingTask.class);

    @Override
    public void run() {
        logger.info("Message");
    }
}

In this example, the thread will just read a message from the queue and log it in a log file. There’s no value returned from the task.

在这个例子中,线程将只是从队列中读取一个消息,并将其记录在一个日志文件中。该任务没有返回任何值。

We can launch the task using ExecutorService:

我们可以使用ExecutorService启动任务。

public void executeTask() {
    executorService = Executors.newSingleThreadExecutor();
    Future future = executorService.submit(new EventLoggingTask());
    executorService.shutdown();
}

In this case, the Future object will not hold any value.

在这种情况下,未来对象将不持有任何价值。

3.2. With Callable

3.2.使用可调用

The Callable interface is a generic interface containing a single call() method that returns a generic value V:

Callable接口是一个通用接口,包含一个call()方法,该方法返回一个通用值V

public interface Callable<V> {
    V call() throws Exception;
}

Let’s look at calculating the factorial of a number:

让我们看看如何计算一个数字的阶乘。

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;
    }
}

The result of call() method is returned within a Future object:

call() 方法的结果在Future 对象中返回。

@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
    FactorialTask task = new FactorialTask(5);
    Future<Integer> future = executorService.submit(task);
 
    assertEquals(120, future.get().intValue());
}

4. Exception Handling

4.异常处理

Let’s see how suitable they are for exception handling.

让我们看看它们对异常处理的适合程度。

4.1. With Runnable

4.1.使用Runnable

Since the method signature does not have the “throws” clause specified, we don’t have a way to propagate further checked exceptions.

由于方法签名中没有指定 “throws “子句,我们没有办法进一步传播检查过的异常。

4.2. With Callable

4.2.使用可调用

Callable‘s call() method contains the “throws Exception” clause, so we can easily propagate checked exceptions further:

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");
        }
    // ...
    }
}

In case of running a Callable using an ExecutorService, the exceptions are collected in the Future object. We can check this by making a call to the Future.get() method.

在使用 ExecutorService运行Callable的情况下,异常被收集在 Future对象中。我们可以通过调用Future.get() 方法来检查这一点。

This will throw an ExecutionException, which wraps the original exception:

这将抛出一个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();
}

In the above test, the ExecutionException is thrown since we are passing an invalid number. We can call the getCause() method on this exception object to get the original checked exception.

在上面的测试中,ExecutionException被抛出,因为我们传递的是一个无效的数字。我们可以在这个异常对象上调用getCause() 方法,以获得原始的检查异常。

If we don’t make the call to the get() method of Future class, the exception thrown by call() method will not be reported back, and the task will still be marked as completed:

如果我们不对Future class的get()方法进行调用,call()方法抛出的异常将不会被反馈,而任务仍将被标记为完成。

@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future<Integer> future = executorService.submit(task);
 
    assertEquals(false, future.isDone());
}

The above test will pass successfully even though we’ve thrown an exception for the negative values of the parameter to FactorialCallableTask.

尽管我们为FactorialCallableTask的参数的负值抛出了一个异常,但上述测试将成功通过。

5. Conclusion

5.结论

In this article, we explored the differences between the Runnable and Callable interfaces.

在这篇文章中,我们探讨了RunnableCallable接口之间的区别。

As always, the complete code for this article is available over on GitHub.

与往常一样,本文的完整代码可在GitHub上获得over