Разница между вызываемым и запускаемым

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

1. Введение

1.1.Рабочий интерфейс

Runnable — это базовый интерфейс, и мы можем выполнить его реализующие экземпляры как Thread или отправить в ExecutorService. Этот интерфейс содержит только один абстрактный метод run(), который мы должны переопределить, чтобы определить задание Thread. Начиная с Java 8, Runnable — это функциональный интерфейс.

@FunctionalInterfacepublic interface Runnable {public abstract void run();}

1.2 Вызываемый интерфейс

Callable также является одним из основных интерфейсов, и они могут быть выполнены только через ExecutorService, а не традиционным классом Thread. Он содержит один абстрактный метод call(), который должен содержать бизнес-логику, которая должна быть выполнена ExecutorService. Callable также является функциональным интерфейсом.

@FunctionalInterfacepublic interface Callable<V> {V call() throws Exception;}

2. Различия между Runnable и Callable

Давайте теперь рассмотрим некоторые основные различия между двумя интерфейсами:

2.1 Методы переопределения

Чтобы использовать интерфейс Runnable, нам необходимо переопределить метод run().

class CallableTask implements Callable<String>{public String call() throws Exception{return "Returning from callable";}} 

Чтобы использовать интерфейс Callable, нам необходимо переопределить метод call().

class RunnableTask implements Runnable {public void run() {System.out.println("Thread executed !");}}

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

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

Запускаемые экземпляры могут быть запущены как классом Thread, так и ExecutorService.

RunnableTask task = new RunnableTask();Thread thread = new Thread(task);thread.start();

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

ExecutorService executor = Executors.newFixedThreadPool(2);CallableTask task = new CallableTask();Future<String> future = executor.submit(task);

2.3 Типы возвращаемых данных

В Runnable возвращаемый тип run() — void, поэтому этот метод не может возвращать никаких значений.

public void run();

В Callable метод call() возвращает объект Future, который предоставляет методы для получения результата вычисления и проверки того, была ли задача завершена или отменена.

public Object call() throws Exception;

2.4 Проверенные исключения

В Runnable метод run() не может выдавать проверенные исключения, поэтому у нас нет способа распространять проверенные исключения. Мы должны обрабатывать проверенные исключения внутри run(), используя только блок try/catch.

В следующем примере конструктор FileInputStream выдает проверенное исключение FileNotFoundException. Мы должны обработать исключение в блоке catch, иначе код не скомпилируется.

class FileReaderTask implements Runnable {public void run(){try(FileInputStream fis = new FileInputStream("file-path")){//read file} catch(FileNotFoundException e) {//handle exception} catch(IOException e) {//handle exception}}}

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

class FileReaderTask implements Callable {public Object call() throws IOException {try(FileInputStream fis = new FileInputStream("file-path")){//read file}return null;}}

На стороне метода вызывающего объекта ExecutionException выбрасывается, когда мы вызываем метод Future.get(). Если мы не вызываем метод get(), исключение будет потеряно, а поток будет помечен как завершенный.

Future<Integer> future = executorService.submit(callableTask); //an exception is thrown from callable taskfuture.isDone(); //true - no exception reportedfuture.get()...; //throws ExecutionException

3. Когда использовать

Интерфейсы Runnable и Callable имеют свои применения, и фреймворк Executor в Java поддерживает оба. Runnable существует уже давно, но он все еще используется и является основным интерфейсом для проектирования параллельных приложений.

  • Runnable не возвращает никакого значения, поэтому мы можем использовать его для вызовов по принципу «запустил и забыл», особенно когда нас не интересует результат выполнения задачи.
  • Вызываемые методы могут создавать исключения и возвращать значения, поэтому они лучше подходят для задач, предполагающих получение результата(например, извлечение ресурса из сети, выполнение дорогостоящих вычислений для получения некоторого значения и т. д.).

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

Мы узнали о Java Runnable и Callable Interfaces с примерами. Мы также увидели некоторые из основных различий между ними и то, как решить, какой из них выбрать при работе в многопоточной среде.

Прокрутить вверх