Август 02, 2021
ExecutorService в Java и его разновидности

Для параллельных вычислений Java предоставляет интерфейс Executor, который помогает отделить описание задач от того, как они будут выполнены, как будут использоваться потоки, как будет производиться scheduling и т.д.

Определение интерфейса выглядит так:


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

Интерфейс принимает задачи в виде экземпляров класса Runnable. В определенный момент времени один из потоков "подбирает" задачу и исполняет ее, вызывая метод Runnable::run. Данный интерфейс имеет множество реализаций, предназначенных для разных типов задач.

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

  • Сколько параллельных потоков нужно запускать по умолчанию?
  • Что нужно делать с новыми задачами, если все доступные потоки заняты?
  • Нужно ли ограничивать размер очереди задач и что делать, если она переполнена?

Для явного контроля всех необходимых параметров можно создать свой собственный ThreadPoolExecutor. Его конструктор выглядит следующим образом:


public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

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

  • int corePoolSize - число потоков, которые должны находиться в пуле, даже если они бездействуют (начальное число потоков)
  • int maximumPoolSize - максимальное число потоков, которым разрешено находиться в пуле (увеличивать ли число потоков вплоть до максимального, если все доступные потоки заняты)
  • long keepAliveTime, TimeUnit unit - максимальное время бездействия до завершения тех потоков, которые были созданы сверх corePoolSize (завершать ли дополнительные потоки, если они бездействуют)
  • BlockingQueue<Runnable> workQueue - очередь, используемая для хранения задач, ожидающих исполнения (как хранить задачи, которые не могут быть исполнены прямо сейчас, и должен ли быть ограничен размер такой очереди)
  • ThreadFactory threadFactory - фабрика, используемая для создания новых потоков
  • RejectedExecutionHandler handler - обработчик, используемый в ситуации, когда достигнуты лимиты размеров пула или очереди (что делать, если мы не можем исполнить задачу)

Пакет java.util.concurrent предоставляет несколько фабричных методов, которые помогают создать пул, используя различные реализации интерфейса Executor и различные параметры инициализации.

  • newFixedThreadPool(int nThreads). Создает пул, который использует фиксированное число потоков и безлимитную очередь. Потоки находятся в пуле до тех пор, пока явно не будут остановлены. Хорошо подходит для задач, интенсивно использующих вычислительные ресурсы процессора.
  • newWorkStealingPool(int parallelism). Создает пул, который держит достаточное количество потоков для поддержания заданного уровня параллелизма. Фактическое количество потоков может увеличиваться и уменьшаться динамически. Пул, созданный при помощи данного фабричного метода, может уменьшить количество конфликтов путем использования нескольких очередей. Хорошо подходит для высоконагруженных сред и для рекурсивных задач.
  • newSingleThreadExecutor(). Создает пул, в котором всегда есть только один поток, и который использует очередь неограниченного размера. Не имеет никаких параметров для конфигурирования и полезен только в тех случаях, когда нужно последовательное исполнение задач в определенном порядке.
  • newCachedThreadPool(). Создает пул, в котором каждый раз при необходимости создаются новые потоки, но, если есть доступные потоки из уже созданных, они будут переиспользованы. Не использует очередь. Если поток бездействует в течении минуты, он будет завершен и удален из кэша. Данный пул полезно использовать в программах, в которых исполняется много короткоживущих задач.
Теги: ,
Добавить комментарий:
Комментариев: (0)
Опубликовать