Зазвичай під час розробки багатопотокового застосунку ми стикаємося з організацією роботи потоків. Чим більший наш застосунок і чим більше потоків нам потрібно для організації виконання багатопотокових завдань, тим більше об'єктів Runnable ми створюємо.
Тут варто зазначити, що створення потоку в Java — доволі дорога операція. Якщо ми будемо створювати новий екземпляр потоку кожного разу для виконання операції, ми матимемо величезні проблеми з продуктивністю і в результаті — з працездатністю застосунку.
Тут нам на допомогу приходить Thread pool (пул потоків) та ThreadPoolExecutor.
Thread pool — це набір заздалегідь ініціалізованих потоків, розмір якого може бути як фіксованим, так і змінним.

Якщо завдань більше, ніж потоків, то вони очікують в черзі (Task Queue). З черги завдання попадає на виконання до N-ного потоку з пула, а після виконання завдання потік забирає нове завдання з черги. Після виконання всіх завдань із черги потоки залишаються активними і чекають на нові завдання. Коли завдання з'являються, потоки починають виконувати і їх.
ThreadPoolExecutor
Із 5-ої версії Java у вирішенні багатопоточності з'являється 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 – створює пул потоків, який може планувати виконання команд після встановленої затримки або для періодичного виконання. |
|
Кожен із видів пулів ми розглянемо у наступних лекціях.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ