Разница между интерфейсами Runnable и Callable в Java
Разница между Runnable и Callable в Java при проектировании многопоточности
Я работаю над проектом на Java, в котором требуется реализовать многопоточность, и столкнулся с выбором между использованием интерфейсов Runnable и Callable. Можете пояснить, в чем основные различия между этими двумя подходами и в каких случаях следует использовать каждый из них? Какие преимущества и недостатки у каждого из методов?
5 ответ(ов)
В Java Callable и Runnable являются функциональными интерфейсами, но у них есть несколько ключевых отличий:
Метод:
Callableдолжен реализовать методcall(), тогда какRunnableреализует методrun().public interface Runnable { void run(); } public interface Callable<V> { V call() throws Exception; }Возврат значения:
Callableможет возвращать значение при выполнении, в отличие отRunnable, который не возвращает никакого значения. Это означает, что вы можете использоватьCallableдля получения результата выполнения:Callable<Integer> callableTask = () -> { // Некоторая логика return 42; };Обработка исключений:
Callableможет выбрасывать проверенные исключения (checked exceptions), тогда как вRunnableэто не предусмотрено. Это позволяетCallableобрабатывать более сложные ситуации:Callable<Void> callableTask = () -> { // Логика, которая может выбросить исключение throw new IOException("Ошибка ввода/вывода"); };Использование с
ExecutorService:Callableможет быть использован с методамиExecutorService#invokeXXX(Collection<? extends Callable<T>> tasks), в то время какRunnableне может быть использован в этом контексте. Это связано с тем, чтоinvokeXXXвозвращает результат выполненияCallableзадач.
Таким образом, если вам необходимо выполнять параллельные задачи, которые возвращают результат и могут выбрасывать исключения, вам стоит использовать Callable. В противном случае, если результат не важен и не ожидаются исключения, вы можете использовать Runnable.
Давайте рассмотрим, где следует использовать интерфейсы 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, ему придется заблокироваться, пока все другие элементы в очереди не будут выполнены. Только после этого метод вернет значения. Это механизм синхронизации.
Интерфейс 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();
}
Вкратце, некоторые заметные отличия между этими интерфейсами:
- Объект
Runnableне возвращает результат, в то время как объектCallableвозвращает результат. - Объект
Runnableне может выбрасывать проверяемые исключения, тогда как объектCallableможет выбрасывать исключения. - Интерфейс
Runnableсуществует с Java 1.0, тогда какCallableбыл введен только в Java 1.5.
Некоторые сходства включают:
- Экземпляры классов, реализующих интерфейсы
RunnableилиCallable, могут быть выполнены в другом потоке. - Экземпляры обоих интерфейсов
CallableиRunnableмогут быть выполнены с помощьюExecutorServiceчерез методsubmit(). - Оба являются функциональными интерфейсами и могут быть использованы в лямбда-выражениях, начиная с Java 8.
Методы интерфейса ExecutorService выглядят следующим образом:
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
Как уже упоминалось, интерфейс Callable является относительно новым и был введен в рамках пакета для работы с параллелизмом. И Callable, и Runnable могут использоваться с исполнителями (executors). Класс Thread (который сам реализует интерфейс Runnable) поддерживает только Runnable.
Тем не менее, вы все равно можете использовать Runnable с исполнителями. Преимущество Callable в том, что вы можете передать его исполнителю и сразу получить обратно объект Future, который будет обновлен, когда выполнение завершится. То же самое можно реализовать с помощью Runnable, но в этом случае вам придется самостоятельно управлять результатами. Например, вы можете создать очередь для результатов, которая будет хранить все результаты. Другой поток может ждать в этой очереди и обрабатывать поступившие результаты.
Разница между Callable и Runnable следующие:
Callableбыл введен в JDK 5.0, в то время какRunnableсуществует с JDK 1.0.- У
Callableесть методcall(), а уRunnable— методrun(). - Метод
callвозвращает значение, тогда как методrunничего не возвращает. - Метод
callможет выбрасывать проверяемые исключения, в то время как методrunне может этого сделать. - Для добавления задачи в очередь
Callableиспользует методsubmit(), аRunnable— методexecute().
"implements Runnable" против "extends Thread" в Java: что выбрать?
Что значит 'synchronized'?
Следует ли использовать отдельные экземпляры ScriptEngine и CompiledScript для каждого потока?
SwingUtilities.invokeLater: Вызов кода в потоке событий Swing
Анимации могут выполняться только на потках Looper в Android