Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Вивчайте Виконавці та Пул Потоків | Механізми Синхронізації Високого Рівня
Багатопотоковість у Java

bookВиконавці та Пул Потоків

Ми вже розглянули різноманітні механізми підтримки багатопотоковості, і Executors є одним із них!

Що таке Executors і пул потоків?

Executors — це механізм, який надає високорівневі абстракції для роботи з потоками. Він дозволяє створювати та керувати пулом потоків, який складається з набору заздалегідь створених потоків, готових до виконання завдань. Замість створення нового потоку для кожного завдання, завдання надсилаються до пулу, де їх виконання розподіляється між потоками.

Що ж таке пул потоків? Це колекція заздалегідь створених потоків, готових виконувати завдання. Використовуючи пул потоків, ви уникаєте витрат на створення та знищення потоків щоразу, оскільки одні й ті самі потоки можуть використовуватися для декількох завдань.

Note
Примітка

Якщо завдань більше, ніж потоків, завдання очікують у Task Queue. Завдання з черги обробляється доступним потоком з пулу, і після завершення завдання потік бере нове завдання з черги. Коли всі завдання в черзі виконані, потоки залишаються активними та очікують нових завдань.

Приклад з життя

Уявіть ресторан, де кухарі (потоки) готують замовлення (завдання). Замість того, щоб наймати нового кухаря для кожного замовлення, ресторан утримує обмежену кількість кухарів, які виконують замовлення по мірі їх надходження. Коли один кухар завершує замовлення, він береться за наступне, що дозволяє ефективно використовувати ресурси ресторану.

Основний метод

newFixedThreadPool(int n): Створює пул із фіксованою кількістю потоків, що дорівнює n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool(): Створює пул, який може створювати нові потоки за потреби, але повторно використовує доступні потоки, якщо такі є.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Створює пул з одним потоком, який гарантує, що завдання виконуються послідовно, тобто одне за одним. Корисно для завдань, які мають виконуватися у суворій послідовності.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

У всіх наведених прикладах методи класу Executors повертають реалізацію інтерфейсу ExecutorService, який використовується для керування потоками.

ExecutorService надає методи для керування пулом потоків. Наприклад, submit(Runnable task) приймає задачу у вигляді об'єкта Runnable і розміщує її в черзі на виконання. Він повертає об'єкт Future, який можна використовувати для перевірки стану задачі та отримання результату, якщо задача повертає результат.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

Метод shutdown() ініціює коректне завершення роботи пулу потоків. Він припиняє прийом нових задач, але завершує поточні задачі. Після виклику цього методу пул не можна перезапустити.

Метод awaitTermination(long timeout, TimeUnit unit) очікує завершення всіх задач у пулі протягом заданого часу. Це блокуюче очікування, яке дозволяє переконатися, що всі задачі виконані перед остаточним завершенням роботи пулу.

Також ми не згадували про основний інтерфейс, який допомагає відстежувати стан потоку, це інтерфейс Future. Метод submit() інтерфейсу ExecutorService повертає реалізацію інтерфейсу Future.

Якщо потрібно отримати результат виконання потоку, можна використати метод get(). Якщо потік реалізує Runnable, метод get() нічого не повертає, але якщо Callable<T>, він повертає тип T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Також можна використати метод cancel(boolean mayInterruptIfRunning), щоб спробувати скасувати виконання завдання. Якщо завдання ще не розпочато, воно буде скасоване. Якщо завдання вже виконується, воно може бути перерване залежно від прапорця mayInterruptIfRunning.

true: Якщо завдання виконується, воно буде перерване шляхом виклику Thread.interrupt() у потоці, що виконує завдання. false: Якщо завдання виконується, воно не буде перерване, і спроба скасування не вплине на поточне виконання завдання.

А також 2 методи, інтуїтивно зрозумілі за своєю функцією:

  • isCancelled(): Перевіряє, чи було скасовано завдання;
  • isDone(): Перевіряє, чи було завершено завдання.

Приклад використання

Note
Примітка

Розмір пулу потоків залежить від характеру виконуваних завдань. Зазвичай розмір пулу потоків не слід жорстко задавати; натомість він має бути налаштовуваним. Оптимальний розмір визначається шляхом моніторингу пропускної здатності виконуваних завдань.

Максимально ефективно використовувати кількість threads = processor cores. Це можна побачити у коді за допомогою Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Відмінності між безпосереднім створенням потоків і використанням ExecutorService

Основні відмінності між безпосереднім створенням потоків і використанням ExecutorService полягають у зручності та керуванні ресурсами. Ручне створення потоків вимагає індивідуального керування кожним потоком, що ускладнює код і адміністрування.

ExecutorService спрощує керування за рахунок використання пулу потоків, що полегшує обробку завдань. Крім того, ручне створення потоків може призвести до високого споживання ресурсів, тоді як ExecutorService дозволяє налаштовувати розмір пулу потоків.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 6

Запитати АІ

expand

Запитати АІ

ChatGPT

Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат

Suggested prompts:

Can you explain more about how ExecutorService manages threads?

What are some best practices for using thread pools?

How does the Future interface help in managing task results?

Awesome!

Completion rate improved to 3.33

bookВиконавці та Пул Потоків

Свайпніть щоб показати меню

Ми вже розглянули різноманітні механізми підтримки багатопотоковості, і Executors є одним із них!

Що таке Executors і пул потоків?

Executors — це механізм, який надає високорівневі абстракції для роботи з потоками. Він дозволяє створювати та керувати пулом потоків, який складається з набору заздалегідь створених потоків, готових до виконання завдань. Замість створення нового потоку для кожного завдання, завдання надсилаються до пулу, де їх виконання розподіляється між потоками.

Що ж таке пул потоків? Це колекція заздалегідь створених потоків, готових виконувати завдання. Використовуючи пул потоків, ви уникаєте витрат на створення та знищення потоків щоразу, оскільки одні й ті самі потоки можуть використовуватися для декількох завдань.

Note
Примітка

Якщо завдань більше, ніж потоків, завдання очікують у Task Queue. Завдання з черги обробляється доступним потоком з пулу, і після завершення завдання потік бере нове завдання з черги. Коли всі завдання в черзі виконані, потоки залишаються активними та очікують нових завдань.

Приклад з життя

Уявіть ресторан, де кухарі (потоки) готують замовлення (завдання). Замість того, щоб наймати нового кухаря для кожного замовлення, ресторан утримує обмежену кількість кухарів, які виконують замовлення по мірі їх надходження. Коли один кухар завершує замовлення, він береться за наступне, що дозволяє ефективно використовувати ресурси ресторану.

Основний метод

newFixedThreadPool(int n): Створює пул із фіксованою кількістю потоків, що дорівнює n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool(): Створює пул, який може створювати нові потоки за потреби, але повторно використовує доступні потоки, якщо такі є.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Створює пул з одним потоком, який гарантує, що завдання виконуються послідовно, тобто одне за одним. Корисно для завдань, які мають виконуватися у суворій послідовності.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

У всіх наведених прикладах методи класу Executors повертають реалізацію інтерфейсу ExecutorService, який використовується для керування потоками.

ExecutorService надає методи для керування пулом потоків. Наприклад, submit(Runnable task) приймає задачу у вигляді об'єкта Runnable і розміщує її в черзі на виконання. Він повертає об'єкт Future, який можна використовувати для перевірки стану задачі та отримання результату, якщо задача повертає результат.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

Метод shutdown() ініціює коректне завершення роботи пулу потоків. Він припиняє прийом нових задач, але завершує поточні задачі. Після виклику цього методу пул не можна перезапустити.

Метод awaitTermination(long timeout, TimeUnit unit) очікує завершення всіх задач у пулі протягом заданого часу. Це блокуюче очікування, яке дозволяє переконатися, що всі задачі виконані перед остаточним завершенням роботи пулу.

Також ми не згадували про основний інтерфейс, який допомагає відстежувати стан потоку, це інтерфейс Future. Метод submit() інтерфейсу ExecutorService повертає реалізацію інтерфейсу Future.

Якщо потрібно отримати результат виконання потоку, можна використати метод get(). Якщо потік реалізує Runnable, метод get() нічого не повертає, але якщо Callable<T>, він повертає тип T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Також можна використати метод cancel(boolean mayInterruptIfRunning), щоб спробувати скасувати виконання завдання. Якщо завдання ще не розпочато, воно буде скасоване. Якщо завдання вже виконується, воно може бути перерване залежно від прапорця mayInterruptIfRunning.

true: Якщо завдання виконується, воно буде перерване шляхом виклику Thread.interrupt() у потоці, що виконує завдання. false: Якщо завдання виконується, воно не буде перерване, і спроба скасування не вплине на поточне виконання завдання.

А також 2 методи, інтуїтивно зрозумілі за своєю функцією:

  • isCancelled(): Перевіряє, чи було скасовано завдання;
  • isDone(): Перевіряє, чи було завершено завдання.

Приклад використання

Note
Примітка

Розмір пулу потоків залежить від характеру виконуваних завдань. Зазвичай розмір пулу потоків не слід жорстко задавати; натомість він має бути налаштовуваним. Оптимальний розмір визначається шляхом моніторингу пропускної здатності виконуваних завдань.

Максимально ефективно використовувати кількість threads = processor cores. Це можна побачити у коді за допомогою Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Відмінності між безпосереднім створенням потоків і використанням ExecutorService

Основні відмінності між безпосереднім створенням потоків і використанням ExecutorService полягають у зручності та керуванні ресурсами. Ручне створення потоків вимагає індивідуального керування кожним потоком, що ускладнює код і адміністрування.

ExecutorService спрощує керування за рахунок використання пулу потоків, що полегшує обробку завдань. Крім того, ручне створення потоків може призвести до високого споживання ресурсів, тоді як ExecutorService дозволяє налаштовувати розмір пулу потоків.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 6
some-alt