5

Разница между интерфейсами Runnable и Callable в Java

12

Разница между Runnable и Callable в Java при проектировании многопоточности

Я работаю над проектом на Java, в котором требуется реализовать многопоточность, и столкнулся с выбором между использованием интерфейсов Runnable и Callable. Можете пояснить, в чем основные различия между этими двумя подходами и в каких случаях следует использовать каждый из них? Какие преимущества и недостатки у каждого из методов?

5 ответ(ов)

1

В Java Callable и Runnable являются функциональными интерфейсами, но у них есть несколько ключевых отличий:

  1. Метод: Callable должен реализовать метод call(), тогда как Runnable реализует метод run().

    public interface Runnable {
        void run();
    }
    
    public interface Callable<V> {
        V call() throws Exception;
    }
    
  2. Возврат значения: Callable может возвращать значение при выполнении, в отличие от Runnable, который не возвращает никакого значения. Это означает, что вы можете использовать Callable для получения результата выполнения:

    Callable<Integer> callableTask = () -> {
        // Некоторая логика
        return 42;
    };
    
  3. Обработка исключений: Callable может выбрасывать проверенные исключения (checked exceptions), тогда как в Runnable это не предусмотрено. Это позволяет Callable обрабатывать более сложные ситуации:

    Callable<Void> callableTask = () -> {
        // Логика, которая может выбросить исключение
        throw new IOException("Ошибка ввода/вывода");
    };
    
  4. Использование с ExecutorService: Callable может быть использован с методами ExecutorService#invokeXXX(Collection<? extends Callable<T>> tasks), в то время как Runnable не может быть использован в этом контексте. Это связано с тем, что invokeXXX возвращает результат выполнения Callable задач.

Таким образом, если вам необходимо выполнять параллельные задачи, которые возвращают результат и могут выбрасывать исключения, вам стоит использовать Callable. В противном случае, если результат не важен и не ожидаются исключения, вы можете использовать Runnable.

0

Давайте рассмотрим, где следует использовать интерфейсы Runnable и Callable.

Оба интерфейса выполняются в другом потоке, отличном от вызывающего, однако Callable может возвращать значение, тогда как Runnable - нет. Где же это действительно имеет значение?

Runnable: Если у вас есть задача, которую нужно просто выполнить и больше не беспокоиться об этом, используйте Runnable. Заключите ваш код в Runnable, и когда будет вызван метод run(), ваше задание будет выполнено. Вызывающий поток не заботится о том, когда будет выполнена ваша задача.

Callable: Если вам нужно получить значение из задачи, используйте Callable. Но сам по себе Callable не выполнит задачу. Вам понадобится объект Future, который вы обернете вокруг вашего Callable, чтобы получить значения при вызове future.get(). В этом случае вызывающий поток будет заблокирован до тех пор, пока Future не вернет результаты, а это может произойти только после выполнения метода call() вашего Callable.

Рассмотрите ситуацию с интерфейсом для целевого класса, в котором определены методы, оборачивающие как Runnable, так и Callable. Вызывающий класс будет случайным образом вызывать методы вашего интерфейса, не зная, какой из них является Runnable, а какой Callable. Методы Runnable будут выполняться асинхронно, пока не будет вызван метод Callable. В этом случае поток вызывающего класса будет заблокирован, поскольку вы извлекаете значения из целевого класса.

Примечание: Внутри вашего целевого класса можно вызывать Callable и Runnable на одном исполнительном потоке, что делает этот механизм похожим на последовательную очередь обработки. Таким образом, пока вызывающий класс вызывает ваши методы, обернутые в Runnable, поток вызывающего класса будет выполняться очень быстро без блокировки. Как только он вызовет метод, обернутый в Callable, ему придется заблокироваться, пока все другие элементы в очереди не будут выполнены. Только после этого метод вернет значения. Это механизм синхронизации.

0

Интерфейс Callable объявляет метод call(), который должен возвращать результат с указанным типом с помощью дженериков. Вот как это выглядит:

public interface Callable<V> {
    /**
     * Вычисляет результат или выбрасывает исключение, если это невозможно.
     *
     * @return вычисленный результат
     * @throws Exception если невозможно вычислить результат
     */
    V call() throws Exception;
}

С другой стороны, Runnable — это интерфейс, который объявляет метод run(), который вызывается при создании потока с использованием реализации этого интерфейса и вызове метода start(). Вы также можете напрямую вызвать метод run(), но в этом случае он просто выполнится в том же потоке.

public interface Runnable {
    /**
     * Когда объект, реализующий интерфейс <code>Runnable</code>, используется
     * для создания потока, запуск этого потока вызывает метод
     * <code>run</code> этого объекта в отдельно выполняющемся
     * потоке.
     * <p>
     * Общий контракт метода <code>run</code> заключается в том, что он может
     * выполнять любые действия.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

Вкратце, некоторые заметные отличия между этими интерфейсами:

  1. Объект Runnable не возвращает результат, в то время как объект Callable возвращает результат.
  2. Объект Runnable не может выбрасывать проверяемые исключения, тогда как объект Callable может выбрасывать исключения.
  3. Интерфейс Runnable существует с Java 1.0, тогда как Callable был введен только в Java 1.5.

Некоторые сходства включают:

  1. Экземпляры классов, реализующих интерфейсы Runnable или Callable, могут быть выполнены в другом потоке.
  2. Экземпляры обоих интерфейсов Callable и Runnable могут быть выполнены с помощью ExecutorService через метод submit().
  3. Оба являются функциональными интерфейсами и могут быть использованы в лямбда-выражениях, начиная с Java 8.

Методы интерфейса ExecutorService выглядят следующим образом:

<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
0

Как уже упоминалось, интерфейс Callable является относительно новым и был введен в рамках пакета для работы с параллелизмом. И Callable, и Runnable могут использоваться с исполнителями (executors). Класс Thread (который сам реализует интерфейс Runnable) поддерживает только Runnable.

Тем не менее, вы все равно можете использовать Runnable с исполнителями. Преимущество Callable в том, что вы можете передать его исполнителю и сразу получить обратно объект Future, который будет обновлен, когда выполнение завершится. То же самое можно реализовать с помощью Runnable, но в этом случае вам придется самостоятельно управлять результатами. Например, вы можете создать очередь для результатов, которая будет хранить все результаты. Другой поток может ждать в этой очереди и обрабатывать поступившие результаты.

0

Разница между Callable и Runnable следующие:

  1. Callable был введен в JDK 5.0, в то время как Runnable существует с JDK 1.0.
  2. У Callable есть метод call(), а у Runnable — метод run().
  3. Метод call возвращает значение, тогда как метод run ничего не возвращает.
  4. Метод call может выбрасывать проверяемые исключения, в то время как метод run не может этого сделать.
  5. Для добавления задачи в очередь Callable использует метод submit(), а Runnable — метод execute().
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь