Обычно, разрабатывая многопоточное приложение, мы сталкиваемся с организацией работы потоков. Чем больше наше приложение и чем больше потоков нам требуется для организации выполнения многопоточных задач, тем больше объектов Runnable мы создаем.
Тут необходимо отметить, что создание потока в Java — достаточно дорогостоящая операция. Если мы будем создавать новый экземпляр потока каждый раз для выполнения операции, мы получим большие проблемы с производительностью и в итоге – с работоспособностью приложения.
Тут нам на помощь приходит Thread pool (пул потоков) и ThreadPoolExecutor.
Thread pool — это набор предварительно инициализированных потоков, размер которого может быть как фиксированным, так и переменным.
Если задач больше, чем потоков, то задачи ждут в очереди (Task Queue). Из очереди задача попадает на исполнение к N-ному потоку из пула, а после выполнения задачи поток забирает новую задачу из очереди. После выполнения всех задач из очереди потоки остаются активными и ждут новых задач. Когда задачи появляются, потоки начинают выполнять и их.
ThreadPoolExecutor
Начиная с Java 5 в решении многопоточности появляется Executor framework. В целом он имеет множество компонентов и пришел к нам, чтобы решить проблему эффективного управления очередью и пулом потоков.
Главные интерфейсы — Executor и ExecutorService.
Executor — интерфейс с единственным методом void execute(Runnable runnable).
Передавая в реализацию этого метода задачу, знай: она будет выполнена асинхронно в будущем.
ExecutorService — интерфейс, который наследуется от интерфейса Executor и предоставляет возможности для выполнения заданий. У него также есть методы для прерывания выполняемой задачи и завершения работы пула потоков.
ThreadPoolExecutor реализует интерфейсы Executor и ExecutorService и разделяет создание задачи и ее выполнение. Нам необходимо реализовать объекты Runnable и отправить их исполнителю, а ThreadPoolExecutor отвечает за их исполнение, создание экземпляров и работу с потоками.
После того, как задача отправляется на исполнение, используется уже созданный поток из пула. Тем самым решается вопрос траты ресурсов на создание и инициализацию нового потока, а после использования – на его очистку GC-ом – и повышается производительность.
ThreadPoolExecutor имеет 4 конструктора:
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
В конструктор передаются параметры ThreadPoolExecutor:
corePoolSize | Параметр, который показывает, какое количество потоков будет готово (запущено) при старте executor сервиса. |
maximumPoolSize | Максимальное количество потоков, которое может создать executor сервис. |
keepAliveTime | Время, в течение которого освободившийся поток будет жить и впоследствии будет уничтожен, если количество потоков больше corePoolSize. Временные единицы указываются в следующем параметре. |
unit | Времненные единицы (часы, минуты, секунды, милисекунды и так далее). |
workQueue | Реализация очереди для задач. |
handler | Обработчик задач, которые не могут быть выполнены. |
threadFactory | Объект, который создает новые потоки по требованию. Использование фабрик потоков устраняет аппаратную привязку вызовов к новому потоку, позволяя приложениям использовать специальные подклассы потоков, приоритеты и т. д. |
Создание ThreadPoolExecutor
Создание ThreadPoolExecutor может нам упростить утилитарный класс Executors. В этом утилитарном классе есть методы, которые нам помогут подготовить объект ThreadPoolExecutor.
newFixedThreadPool — создает пул потоков, который повторно использует фиксированное количество потоков для выполнения любого количества задач. |
|
newWorkStealingPool – создает пул потоков, где количество потоков = количество ядер процессора, которые доступны для JVM. Дефолтный уровень параллелизма – один. Это значит, что в пуле будет создано столько потоков, сколько ядер ЦП доступно JVM. Если параллелизм равен 4, тогда вместо количества ядер используется переданное значение. |
|
newSingleThreadExecutor – создает пул с единственным потоком для выполнения всех задач. |
|
newCachedThreadPool – создает пул потоков, который создает новые потоки по мере необходимости, но будет повторно использовать ранее созданные потоки, когда они будут доступны. |
|
newScheduledThreadPool – создает пул потоков, который может планировать выполнение команд после заданной задержки или для периодического выполнения. |
|
Каждый из видов пулов мы рассмотрим в следующих лекциях.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ