Рубрики
Без рубрики

Запускаемый против Вызываемый на Java

Узнайте разницу между выполняемыми и вызываемыми интерфейсами в Java.

Автор оригинала: baeldung.

1. Обзор

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

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

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

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

3. Возвращаемые Значения

Давайте глубже рассмотрим, как эти интерфейсы обрабатывают возвращаемые значения.

3.1. С возможностью запуска

Интерфейс 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. С возможностью вызова

Вызываемый интерфейс – это универсальный интерфейс, содержащий один метод 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. С возможностью запуска

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

4.2. С возможностью вызова

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

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

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

В случае запуска вызываемого с помощью 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 .

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