Runnable против Callable в Java

Runnable против Вызываемый в Java

1. обзор

С первых дней Java многопоточность была основным аспектом языка. Runnable - это основной интерфейс, предназначенный для представления многопоточных задач, аCallable - это улучшенная версияRunnable, которая была добавлена ​​в Java 1.5.

В этой статье мы рассмотрим различия и возможности применения обоих интерфейсов.

2. Механизм исполнения

Оба интерфейса предназначены для представления задачи, которая может быть выполнена несколькими потоками. ЗадачиRunnable можно запускать с использованием классаThread илиExecutorService, тогда какCallables можно запускать только с использованием последнего.

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 - это общий интерфейс, содержащий единственный методcall(), который возвращает общее значениеV:

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

Давайте посмотрим, как вычислить факториал числа:

public class FactorialTask implements Callable {
    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 future = executorService.submit(task);

    assertEquals(120, future.get().intValue());
}

4. Обработка исключений

Посмотрим, насколько они подходят для обработки исключений.

4.1. СRunnable

Поскольку в сигнатуре метода не указано предложение «throws», __ нет способа распространять дополнительные проверенные исключения.

4.2. СCallable

МетодCallable’s call() содержит предложение throwsException”, поэтому мы можем легко распространять проверенные исключения дальше:

public class FactorialTask implements Callable {
    // ...
    public Integer call() throws InvalidParamaterException {

        if(number < 0) {
            throw new InvalidParamaterException("Number should be positive");
        }
    // ...
    }
}

В случае запускаCallable using иExecutorService, исключения собираются в объектеFuture, что можно проверить, вызвав методFuture.get(). Это вызоветExecutionException –, который обертывает исходное исключение:

@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {

    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future future = executorService.submit(task);
    Integer result = future.get().intValue();
}

В приведенном выше тестеExecutionException выбрасывается, поскольку мы передаем недопустимое число. Мы можем вызвать методgetCause() для этого объекта исключения, чтобы получить исходное проверенное исключение.

Если мы не вызовем методget() классаFuture - тогда исключение, сгенерированное методомcall(), не будет возвращено, и задача все равно будет помечена как завершенная :

@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future future = executorService.submit(task);

    assertEquals(false, future.isDone());
}

Вышеупомянутый тест пройдет успешно, даже если мы создали исключение для отрицательных значений параметраFactorialCallableTask..

5. Заключение

В этой статье мы исследовали различия между интерфейсамиRunnable иCallable.

Как всегда, доступен полный код этой статьиover on GitHub.