— Пересічний програміст рано чи пізно стикається з тим, що має багато маленьких завдань, які потрібно виконувати час від часу.
Якщо ти пишеш гру, то це дії, які виконують окремі персонажі.
Якщо пишеш веб-сервер, то це різні команди, що надходять від користувачів: завантажити фото, перекодувати його в потрібний формат, застосувати потрібний шаблон і т.д.
Всі великі завдання рано чи пізно розбиваються на набір маленьких та зручних завдань.
Ось так на цьому тлі непомітно і виникає питання – а як ними керувати всіма? Якщо за хвилину потрібно виконати кілька сотень завдань? Створювати для кожного завдання свою нитку не дуже раціонально. Для кожної нитки Java-машина виділяє чимало ресурсів.
Іншими словами – створення і знищення нитки, що відпрацювала, може витрачати більше ресурсів і часу, ніж саме виконуване завдання.
Java-розробники вигадали елегантне вирішення цієї проблеми — ThreadPoolExecutor.
ThreadPoolExecutor – це клас, який має всередині дві речі:
А) Черга завдань, до яких можна додавати завдання, в міру їх появи в програмі.
Б) Пул-ниток (група ниток) – які ці завдання виконують.
При цьому нитки не знищуються після виконання завдання, а засипають. Щоб розпочати виконання нового завдання, як тільки воно з'явиться.
При створенні ThreadPoolExecutor, можна задати максимальну кількість ниток, які будуть створені та максимальну кількість завдань, які можна помістити в чергу. Тобто. можна обмежити кількість ниток числом 10, наприклад, а кількість завдань у черзі – 100.
Як працює ThreadPoolExecutor:
1) При додаванні нового завдання воно поміщається в кінець черги.
2) Якщо черга заповнена буде викинуто виняток.
3) Кожна нитка після виконання завдання бере чергове завдання з черги і починає виконувати його.
4) Якщо завдань у черзі немає, нитка засипає до їх додавання.
Підхід з обмеженням кількості ниток, що працюють, вигідний тим, що чим більше ниток, тим сильніше вони один одному заважають. Набагато ефективніше мати 5-10 ниток-виконавців і довгу чергу завдань, ніж створити 100 ниток для групи завдань, що раптово з'явилася, які конкуруватиме один з одним за ресурси: пам'ять, час процесора, доступ до бази тощо.
Приклад такої роботиThreadPoolExecutor:
<
ExecutorService service = Executors.newFixedThreadPool(2 );
for(int i = 0; i < 10; i++)
{
service.submit(new Runnable() {
public void run()
{
// Тут ми завантажуємо щось важке з Інтернету.
}
});
}
— Я щось не бачу його…
— Об'єкт ThreadPoolExecutor створюється при викликі методу newFixedThreadPool.
Так ось працює він дуже просто. Як тільки ти додаєш йому завдання за допомогою методу submit, він:
А) Будить сплячу нитку для її виконання, якщо така є.
Б) Створює нову нитку для виконання завдання, якщо її немає.
В) Якщо досягнуто максимум ниток, то просто кладе завдання в кінець черги.
Я спеціально написала у прикладі – тут ми завантажуємо щось важке з інтернету. Якщо у нас є 100 завдань «завантажити щось велике з інтернету», то немає сенсу запускати багато таких завдань одночасно – ми впораємося в обмеження ширини інтернет-каналу. У такому випадку пари ниток має бути достатньо. Саме це ти й бачиш у прикладі вище:
ExecutorService service = Executors.newFixedThreadPool(2);
— Виявляється, працювати з купою завдань не так вже й складно.
— Так. Навіть легше, ніж ти можеш собі уявити. Але про це тобі розповість Кім.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ