Введение

В предыдущей статье мы обсудили основы потоков и сырые способы создания потоков.



Как уже говорилось, большинство современных приложений редко создают поток, используя обсуждаемый подход. Язык Java предоставляет лучшие альтернативы с языковыми API высокого уровня и параллельными утилитами для написания многопоточных приложений. Более того, многим приложениям в настоящее время требуется не только один поток, а пул потоков для запуска приложения с большей скоростью отклика и высокой пропускной способностью. Этот пул потоков обычно называют пулом потоков, а язык Java поддерживает пул потоков с API Executor Service.

Исполнитель

Это базовый интерфейс в структуре пула потоков Java. Исполнитель — это объект, который может выполнять запускаемую задачу. Этот интерфейс позволяет отделить отправку задач от способа их выполнения. Поэтому вместо того, чтобы создавать один поток и запускать его, выполните задачу следующим образом:

Thread t = new Thread(new Runnable() {
@Override
public void run() {
    System.out.println(“Hello World”);
   }
});
t.start();

Исполнитель может сделать следующим образом

Executor executor = Executors.newFixedThreadPool(1); // Create a thread pool with one Thread
executor.execute(new Runnable() { // Executes the task
@Override
public void run() {
  System.out.println(“Hello World”);
 }
});

Обратите внимание, что создание потока и другие вещи в этом API абстрагированы, и пользователь предоставляет Executor только задачи для выполнения.

В приведенных ниже фрагментах кода показан пример реализации интерфейса Executor вместо использования фиксированного пула потоков:

Исполнитель Сервис

Это базовый интерфейс, на котором реализован пул потоков Java. Этот интерфейс расширяет интерфейс Executor и добавляет методы для управления завершением и отслеживанием отправленных задач. Его метод submit возвращает объект Future, который можно использовать для отмены выполнения/ожидания завершения отправленных задач.

Служба-исполнитель может быть отключена по запросу. shutdown пытается выполнить плавное завершение работы пула, что позволяет завершить все ранее отправленные задачи, но не принимает новые задачи после вызова shutdown. С другой стороны, shutdownNow пытается принудительно отключить пул, предотвращая запуск всех ожидающих задач и пытаясь остановить выполнение текущих задач.

Он также предоставляет методы для выполнения массовой отправки заданий. С помощью invokeAll и invokeAny наборы задач могут быть отправлены сразу, и служба исполнителя попытается вызвать все или любую задачу ( с) соответственно.

Java предоставляет стандартную (абстрактную) реализацию этого интерфейса в AbstractExecutorService. Это, в свою очередь, расширяется с помощью ThreadPoolExecutor, и этот класс обеспечивает полную реализацию пула потоков Java.

Java предоставляет несколько типов реализации пула потоков на основе общего использования.

Будущее

Как уже говорилось, Java обеспечивает богатую и высокоуровневую поддержку API для написания высокопараллельных приложений. Хотя интерфейс runnable предоставляет механизм для запуска задачи в отдельном потоке, он мало что делает с точки зрения мониторинга задач, управления задачами или возврата результатов/исключений в вызывающий поток. Структура Future в Executor Service частично помогает в достижении вышеуказанных целей.

Из документации Java,

Future представляет результат асинхронного вычисления. Предоставляются методы для проверки завершения вычисления, ожидания его завершения и извлечения результата вычисления. Результат можно получить с помощью метода get только после завершения вычисления, блокируя при необходимости до тех пор, пока он не будет готов. Отмена производится методом cancel. Предусмотрены дополнительные методы для определения того, завершилась ли задача нормально или была отменена. После завершения вычисления его нельзя отменить. Если вы хотите использовать Future ради возможности отмены, но не предоставлять полезный результат, вы можете объявить типы формы Future<?> и вернуть null в результате базовой задачи.

Вызываемый

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

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

В приведенном ниже коде показан пример Callable и Future:

ThreadPoolExecutor — это основная реализация пула потоков и основа всех типов пулов потоков, предоставляемых в Java. Хотя этот класс обеспечивает полную реализацию пула потоков, программисты часто используют более конкретные типы пулов потоков, например. FixedThreadPool, CachedThreadPool, SingleThreadExeutor, WorkStealingPool и т. д. в зависимости от потребностей обработки задачи. Например, FixedThreadPool предоставляет фиксированный набор потоков для выполнения задач с LinkedBlockingQueue для постановки задач в очередь, если задачи не могут быть выбраны для немедленной обработки. Эта реализация подходит для хорошей оптимизации ресурсов.

Прежде чем продолжить, давайте разберемся, как работает пул потоков внутри. В Java каждый пул потоков имеет следующие компоненты:

  1. Размер основного пула
  2. Максимальный размер пула
  3. Очередь
  4. Фабрика ниток
  5. Обработчик выполнения отклонения

Размер основного пула

Это минимальное количество потоков, которые всегда должны быть доступны в пуле. Это применимо, даже если в пуле нет активных задач для обработки. Даже в случае аварийного завершения потока основного пула Java гарантирует создание потока и сохранение размера основного пула. Однако потоки размера основного пула могут быть завершены, если для флага allowCoreThreadTimeOut установлено значение true. В этом случае потоки основного пула будут завершены по истечении keepAliveTime. В пуле потоков не будет потока, если пул неактивен и время действия keepAliveTime истекает для всех потоков основного пула.

Максимальный размер пула

Это максимальное количество потоков, которое может иметь пул, и не может превышать это значение.

Очередь

Очередь используется внутри для удержания дополнительных задач после того, как все потоки основного пула заняты и недоступны для выполнения новых задач. Обратите внимание, что новый поток создается, если количество запущенных потоков меньше, чем настроенное количество основных потоков опроса, и задание отправляется для обработки, а не для постановки в очередь.

Фабрика потоков

Фабрика потоков используется для создания новых потоков в пуле потоков.

Обработчик отклонения выполнения

Этот обработчик используется, чтобы решить, когда пул потоков не может принимать новые задачи из-за различных сценариев. Это включает в себя достижение максимального размера пула или переполнение очереди. Существуют разные политики обработки задач, которые не могут быть обработаны пулом.

Вывод

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