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

В чем разница между использованием интерфейсов Runnable и Callable при проектировании параллельного потока на Java, почему бы вам выбрать один вместо другого?

Для дополнительного обсуждения после прочтения этой страницы см. Будет ли Callable предпочтительнее Runnable?

barfuin 18.06.2013 12:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
511
1
307 218
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Ответ принят как подходящий

См. Пояснение здесь.

The Callable interface is similar to Runnable, in that both are designed for classes whose instances are potentially executed by another thread. A Runnable, however, does not return a result and cannot throw a checked exception.

Я нашел это в другом блоге, который может объяснить это немного подробнее, эти различия:

Хотя оба интерфейса реализуются классами, которые хотят выполняться в другом потоке выполнения, между двумя интерфейсами есть несколько различий, а именно:

  • Экземпляр Callable<V> возвращает результат типа V, а экземпляр Runnable - нет.
  • Экземпляр Callable<V> может выдавать проверенные исключения, тогда как экземпляр Runnable не может

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

What are the differences in the applications of Runnable and Callable. Is the difference only with the return parameter present in Callable?

В основном да. Смотрите ответы на этот вопрос. И javadoc для Callable.

What is the need of having both if Callable can do all that Runnable does?

Поскольку интерфейс Runnableне могу делает все, что делает Callable!

Runnable существует с Java 1.0, но Callable был представлен только в Java 1.5 ... для обработки сценариев использования, которые Runnable не поддерживает. Теоретически группа разработчиков Java могла бы изменить сигнатуру метода Runnable.run(), но это нарушило бы бинарную совместимость с кодом до версии 1.5, требуя перекодирования при переносе старого кода Java на новые JVM. Это БОЛЬШОЕ НЕТ-НЕТ. Java стремится к обратной совместимости ... и это было одним из главных преимуществ Java для бизнес-вычислений.

И, очевидно, есть варианты использования, когда задача не необходимость возвращает результат или генерирует проверенное исключение. Для таких случаев использование Runnable более кратко, чем использование Callable<Void> и возврат фиктивного (null) значения из метода call().

Интересно, откуда вы взяли эту историю. Это очень полезно.

spiderman 06.05.2014 18:36

@prash - основные факты можно найти в старых учебниках. Как первое издание Java in a Nutshell.

Stephen C 29.05.2014 15:40

(@prash - Также ... начав использовать Java в эпоху Java 1.1.)

Stephen C 24.10.2015 01:43

@StephenC Если я правильно прочитал ваш ответ, вы предполагаете, что Runnable существует (в основном) по причинам обратной совместимости. Но разве нет ситуаций, когда реализовать (или требовать) интерфейс Callable ненужно или слишком дорого (например, в ScheduledFuture<?> ScheduledExecutorService.schedule(Runnable command, long delay, TimeUnit unit))? Так разве нет пользы в том, чтобы поддерживать оба интерфейса на языке, даже если история не привела к текущему результату?

max 04.05.2017 14:46

@max - я с этим не согласен. Если вы посмотрите на эти методы API, то увидите, что сложность и стоимость не равны вызванный при использовании Callable в качестве типа аргумента. C&C заложены в функциональности. (Или, другими словами, эти методы были бы в равной степени C & Cly, если бы они использовали Runnable в качестве типа аргумента.) Итак, нет, это не причина для сохранения как Callable, так и Runnable.

Stephen C 05.07.2017 01:36

@StephenC Моя формулировка была плохой. Я хотел сказать, что ваш последний абзац, кажется, дает аргумент в пользу того, что Runnable доступен для ситуаций, когда нет смысла требовать от клиента поддержки интерфейса Callable (не то чтобы его сложно поддерживать, но это все же означает, что клиенту нужен чтобы добавить неиспользуемый шаблонный код).

max 05.07.2017 01:44

@max - Я сказал это и все еще согласен с этим. Однако это вторичная причина. Но даже в этом случае я подозреваю, что Runnableбыл бы изменялся, если не было необходимости поддерживать совместимость. «Шаблон» return null; - слабый аргумент. (По крайней мере, это было бы мое решение ... в гипотетическом контексте, когда вы могли бы игнорировать обратную совместимость.)

Stephen C 05.07.2017 01:51

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

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

Интересно, каков пример исключения потока в Java? сможет ли основной поток поймать это исключение? В противном случае я бы не стал использовать Callable. Алекс, ты что-нибудь понимаешь по этому поводу? Благодарность!

trillions 25.03.2014 13:04

Код, работающий в настраиваемом потоке, как и любой другой код, может вызвать исключение. Чтобы поймать его в другом потоке, вам нужно приложить некоторые усилия либо с использованием настраиваемого механизма уведомления (например, на основе слушателей), либо с помощью Future, либо путем добавления ловушки, которая перехватывает все неперехваченные исключения: docs.oracle.com/javase/6/docs/api/java/lang/…

AlexR 25.03.2014 13:27

Отличная информация! Спасибо, Алекс! :)

trillions 25.03.2014 21:34

Я поддержал этот ответ, потому что он утверждает (правильно, если принимать по номинальной стоимости), что необходимо использовать модель пула потоков с вызываемыми объектами. Видимо, прискорбно то, что нельзя расширить Thread для полноценного использования интерфейса Callable, чтобы можно было настроить отдельный поток для выполнения вызываемых вещей и других вещей, которые могут понадобиться разработчику. Если кто-то, кто читает этот комментарий, думает, что я ошибаюсь, я хотел бы знать получше ...

user1941660 20.10.2015 06:06

@ user1941660 Если вы имели в виду буквально пул потоков, неправильно. Вы можете передать Runnable или Callable службе-исполнителю, поддерживаемой одним потоком, а не пулом потоков. См. Executors.newSingleThreadExecutor() и Executors.newSingleThreadScheduledExecutor(). Если под «пулом потоков» вы имели в виду структуру Executors, имейте в виду, что цель добавления среды Executors в Java 5 заключалась в том, чтобы избавить разработчиков от необходимости иметь дело с классом Thread напрямую. Как правило, вам больше не следует расширять Thread для работы с параллелизмом.

Basil Bourque 26.12.2020 10:28
  • Callable должен реализовывать метод call(), а Runnable должен реализовывать метод run().
  • Callable может возвращать значение, а Runnable - нет.
  • Callable может генерировать проверенное исключение, а Runnable - нет.
  • Callable можно использовать с методами ExecutorService#invokeXXX(Collection<? extends Callable<T>> tasks), но нельзя использовать Runnable.

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

ExecutorService.submit (Runnable task) также существует и очень полезен

Yair Kukielka 29.05.2015 21:00

Runnable также можно использовать с ExecutorService следующими способами: 1) ExecutorService.execute (Runnable) 2) ExecutorService.submit (Runnable)

Azam Khan 11.11.2016 09:08

Также есть Executor.submit (Callable <T> task), но вы не можете invokeAll или invokeAny с коллекцией Runnable tasks Collection <? расширяет задачи Callable <T>>

nikli 02.09.2017 14:48

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

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

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

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

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

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

Интерфейс Callable объявляет метод call(), и вам необходимо предоставить обобщения, поскольку тип Object call () должен возвращать -

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

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

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used 
     * to create a thread, starting the thread causes the object's 
     * <code>run</code> method to be called in that separately executing 
     * thread. 
     * <p>
     * The general contract of the method <code>run</code> is that it may 
     * take any action whatsoever.
     *
     * @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. Оба являются функциональными интерфейсами и могут использоваться в лямбда-выражениях начиная с Java8.

Методы в интерфейсе ExecutorService:

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

Назначение этих интерфейсов из документации Oracle:

Интерфейс Работоспособен должен быть реализован любым классом, экземпляры которого предназначены для выполнения Thread. Класс должен определять метод без аргументов с именем run.

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

Прочие отличия:

  1. Вы можете передать Runnable, чтобы создать Нить. Но вы не можете создать новый поток, передав Callable в качестве параметра. Вы можете передать Callable только экземплярам ExecutorService.

    Пример:

    public class HelloRunnable implements Runnable {
    
        public void run() {
            System.out.println("Hello from a thread!");
        }   
    
        public static void main(String args[]) {
            (new Thread(new HelloRunnable())).start();
        }
    
    }
    
  2. Используйте Runnable для включения и выключения вызовов. Используйте Callable, чтобы проверить результат.

  3. Callable может быть передан методу invokeAll, в отличие от Runnable. Методы invokeAny и invokeAll выполняют наиболее часто используемые формы массового выполнения, выполняя набор задач и ожидая завершения хотя бы одной или всех задач.

  4. Тривиальная разница: имя метода, который будет реализован => run() для Runnable и call() для Callable.

Разница между Callable и Runnable заключается в следующем:

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

Важно подчеркнуть, что проверенное исключение, а не RuntimeException

BertKing 22.07.2019 16:00

+-------------------------------------+--------------------------------------------------------------------------------------------------+
|              Runnable               |                                           Callable<T>                                            |
+-------------------------------------+--------------------------------------------------------------------------------------------------+
| Introduced in Java 1.0 of java.lang | Introduced in Java 1.5 of java.util.concurrent library                                           |
| Runnable cannot be parametrized     | Callable is a parametrized type whose type parameter indicates the return type of its run method |
| Runnable has run() method           | Callable has call() method                                                                       |
| Runnable.run() returns void         | Callable.call() returns a value of Type T                                                        |
| Can not throw Checked Exceptions    | Can throw Checked Exceptions                                                                     |
+-------------------------------------+--------------------------------------------------------------------------------------------------+

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

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

Разница между Работоспособен и вызываемый, как показано ниже:

1) Метод бег()работоспособный возвращает пустота, означает, что если вы хотите, чтобы ваш поток возвращал что-то, что вы можете использовать дальше, у вас есть метод нет выбора с Runnable run (). Есть решение 'Callable'. Если вы хотите вернуть что-либо в виде объект, тогда вы следует использовать Callable вместо Runnable. Вызываемый интерфейс имеет метод 'call ()', который возвращает объект.

Подпись метода - Работоспособен->

public void run(){}

Вызываемый->

public Object call(){}

2) В случае метода Запускаемый бег (), если возникает какое-либо проверенное исключение, вы должны нужно обрабатывать с помощью блока try catch, но в случае метода Вызываемый вызов () вы может выдать проверенное исключение, как показано ниже

 public Object call() throws Exception {}

3) Работоспособен происходит из устаревшей версии Java 1.0, но вызываемый входит в версию Java 1.5 с фреймворком Исполнитель.

Если вы знакомы с Исполнители, вам следует используйте Callable вместо Runnable.

Надеюсь ты понимаешь.

Запускаемый (против) вызываемый вступает в силу, когда мы используем фреймворк Executer.

ExecutorService - это подинтерфейс Executor, который принимает как выполняемые, так и вызываемые задачи.

Более ранняя многопоточность может быть достигнута с использованием интерфейса RunnableSince 1.0, но здесь проблема в том, что после выполнения задачи потока мы не можем собрать информацию о потоках. Для сбора данных мы можем использовать статические поля.

Пример. Отдельные потоки для сбора данных каждого студента.

static HashMap<String, List> multiTasksData = new HashMap();
public static void main(String[] args) {
    Thread t1 = new Thread( new RunnableImpl(1), "T1" );
    Thread t2 = new Thread( new RunnableImpl(2), "T2" );
    Thread t3 = new Thread( new RunnableImpl(3), "T3" );

    multiTasksData.put("T1", new ArrayList() ); // later get the value and update it.
    multiTasksData.put("T2", new ArrayList() );
    multiTasksData.put("T3", new ArrayList() );
}

Чтобы решить эту проблему, они ввели Callable<V>Since 1.5, который возвращает результат и может вызывать исключение.

  • Единый абстрактный метод: Интерфейсы Callable и Runnable имеют один абстрактный метод, что означает, что они могут использоваться в лямбда-выражениях в java 8.

    public interface Runnable {
    public void run();
    }
    
    public interface Callable<Object> {
        public Object call() throws Exception;
    }
    

Есть несколько разных способов делегировать задачи для выполнения ExecutorService.

  • execute(Runnable task):void создает новый поток, но не блокирует основной поток или вызывающий поток, поскольку этот метод возвращает значение void.
  • submit(Callable<?>):Future<?>, submit(Runnable):Future<?> создают новый поток и блокируют основной поток, когда вы используете future.get ().

Пример использования интерфейсов Runnable, Callable с платформой Executor.

class CallableTask implements Callable<Integer> {
    private int num = 0;
    public CallableTask(int num) {
        this.num = num;
    }
    @Override
    public Integer call() throws Exception {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < 5; i++) {
            System.out.println(i + " : " + threadName + " : " + num);
            num = num + i;
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task. Final Value : "+ num);

        return num;
    }
}
class RunnableTask implements Runnable {
    private int num = 0;
    public RunnableTask(int num) {
        this.num = num;
    }
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < 5; i++) {
            System.out.println(i + " : " + threadName + " : " + num);
            num = num + i;
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task. Final Value : "+ num);
    }
}
public class MainThread_Wait_TillWorkerThreadsComplete {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("Main Thread start...");
        Instant start = java.time.Instant.now();

        runnableThreads();
        callableThreads();

        Instant end = java.time.Instant.now();
        Duration between = java.time.Duration.between(start, end);
        System.out.format("Time taken : %02d:%02d.%04d \n", between.toMinutes(), between.getSeconds(), between.toMillis()); 

        System.out.println("Main Thread completed...");
    }
    public static void runnableThreads() throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(4);
        Future<?> f1 = executor.submit( new RunnableTask(5) );
        Future<?> f2 = executor.submit( new RunnableTask(2) );
        Future<?> f3 = executor.submit( new RunnableTask(1) );

        // Waits until pool-thread complete, return null upon successful completion.
        System.out.println("F1 : "+ f1.get());
        System.out.println("F2 : "+ f2.get());
        System.out.println("F3 : "+ f3.get());

        executor.shutdown();
    }
    public static void callableThreads() throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(4);
        Future<Integer> f1 = executor.submit( new CallableTask(5) );
        Future<Integer> f2 = executor.submit( new CallableTask(2) );
        Future<Integer> f3 = executor.submit( new CallableTask(1) );

        // Waits until pool-thread complete, returns the result.
        System.out.println("F1 : "+ f1.get());
        System.out.println("F2 : "+ f2.get());
        System.out.println("F3 : "+ f3.get());

        executor.shutdown();
    }
}

Это своего рода соглашение об именах интерфейсов, которое соответствует функциональному программированию.

//Runnable
interface Runnable {
    void run();
}

//Action - throws exception
interface Action {
    void run() throws Exception;
}

//Consumer - consumes a value/values, throws exception
interface Consumer1<T> {
    void accept(T t) throws Exception;
}

//Callable - return result, throws exception
interface Callable<R> {
    R call() throws Exception;
}

//Supplier - returns result, throws exception
interface Supplier<R> {
    R get() throws Exception;
}

//Predicate - consumes a value/values, returns true or false, throws exception
interface Predicate1<T> {
    boolean test(T t) throws Exception;
}

//Function - consumes a value/values, returns result, throws exception
public interface Function1<T, R> {
    R apply(T t) throws Exception;
}

...

//Executor
public interface Executor {
    void execute(Runnable command);
}

Откуда ты это нашел? Я не могу найти интерфейс действий, соответствующий вашему примеру в Java

Erwan Daniel 20.10.2020 15:04

Это просто общий вид

yoAlex5 20.10.2020 15:13

Другие вопросы по теме