newCachedThreadPool()
проти newFixedThreadPool()
Коли я повинен використовувати те чи інше? Яка стратегія краща з точки зору використання ресурсів?
newCachedThreadPool()
проти newFixedThreadPool()
Коли я повинен використовувати те чи інше? Яка стратегія краща з точки зору використання ресурсів?
Відповіді:
Я думаю, що документи досить добре пояснюють різницю та використання цих двох функцій:
Створює пул потоків, який повторно використовує фіксовану кількість потоків, що працюють поза спільною необмеженою чергою. У будь-якій точці, щонайбільше nThreads буде активним завданням обробки. Якщо додаткові завдання будуть подані, коли всі потоки активні, вони будуть чекати в черзі, поки нитка не буде доступною. Якщо будь-який потік припиняється через збій під час виконання перед відключенням, новий зайняє своє місце, якщо потрібно для виконання подальших завдань. Нитки в пулі існуватимуть до тих пір, поки воно не буде явно закрито.
Створює пул потоків, який створює нові потоки за потребою, але повторно використовує попередньо побудовані нитки, коли вони доступні. Ці пули, як правило, покращують продуктивність програм, які виконують багато короткочасних асинхронних завдань. Виклики для виконання повторно використовуватимуть раніше побудовані потоки, якщо вони доступні. Якщо немає наявного потоку, буде створено та додано нову нитку до пулу. Нитки, які не були використані шістдесят секунд, завершуються та видаляються з кеша. Таким чином, пул, який достатньо довго залишається бездіяльним, не буде споживати жодних ресурсів. Зауважте, що пули з подібними властивостями, але різні деталі (наприклад, параметри тайм-ауту) можуть бути створені за допомогою конструкторів ThreadPoolExecutor.
З точки зору ресурсів, newFixedThreadPool
воля підтримуватиме всі потоки до їх явного припинення. У newCachedThreadPool
потоках, які не були використані шістдесят секунд, вони припиняються та видаляються з кеша.
Враховуючи це, споживання ресурсів буде дуже залежати від ситуації. Наприклад, якщо у вас є величезна кількість тривалих завдань, я б запропонував FixedThreadPool
. Щодо CachedThreadPool
документа, документи кажуть, що "Ці пули, як правило, покращують продуктивність програм, які виконують безліч короткочасних асинхронних завдань".
newCachedThreadPool
може спричинити серйозні проблеми, оскільки ви залишаєте весь контроль thread pool
і коли служба працює з іншими в тому ж хості , що може призвести до збоїв інших людей через тривалий час очікування процесора. Тому я думаю, що newFixedThreadPool
в такому сценарії можна бути більш безпечним. Також цей пост роз'яснює найвизначніші відмінності між ними.
Для завершення інших відповідей я хотів би процитувати Ефективну Java, 2-е видання, Джошуа Блох, глава 10, пункт 68:
"Вибір служби виконавця для конкретної програми може бути складним. Якщо ви пишете невелику програму або злегка завантажений сервер , використовуючи Executors.new- CachedThreadPool, як правило, хороший вибір , оскільки він не вимагає конфігурації. правильна річ ». Але кешований пул потоків - не вдалий вибір для сильно завантаженого сервера виробництва !
У кешуватися пулі потоків , представлені завдання не ставляться в черзі , але негайно передати його в руках нитки для виконання. Якщо потоки недоступні, створюється нова . Якщо сервер настільки сильно завантажений, що всі його процесори повністю використані, і більше завдань надійде, то більше ниток буде створено, що тільки погіршить питання.
Тому на високо завантаженому виробничому сервері вам набагато краще використовувати Executors.newFixedThreadPool , який дає вам пул із фіксованою кількістю потоків або безпосередньо використовуючи клас ThreadPoolExecutor для максимального контролю. "
Якщо ви подивитесь на вихідний код , ви побачите, вони викликають ThreadPoolExecutor. внутрішньо і встановлюючи їх властивості. Ви можете створити свою, щоб краще контролювати свої вимоги.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Якщо вас не турбує безмежна черга завдань, що викликаються / Виконується , ви можете скористатися одним із них. Як запропонував бруно, я теж вважаю за краще newFixedThreadPool
це newCachedThreadPool
над цими двома.
Але ThreadPoolExecutor надає більш гнучкі функції порівняно з будь-яким newFixedThreadPool
абоnewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Переваги:
Ви повністю контролюєте розмір BlockingQueue . Це не без обмежень, на відміну від попередніх двох варіантів. Я не вийду з помилки пам’яті через величезну групу вирішених завдань, що викликаються / Виконується, коли в системі є несподівана турбулентність.
Ви можете реалізувати власну політику обробки відхилень або використовувати одну з політик:
За замовчуванням ThreadPoolExecutor.AbortPolicy
обробник кидає програму RejectedExecutionException при відхиленні.
У ThreadPoolExecutor.CallerRunsPolicy
цьому потоці, що викликає виконання, виконується сама задача. Це забезпечує простий механізм контролю зворотного зв’язку, який уповільнить швидкість подання нових завдань.
У ThreadPoolExecutor.DiscardPolicy
задачі, яку неможливо виконати, просто випадає.
В ThreadPoolExecutor.DiscardOldestPolicy
, якщо виконавець не вимикається, завдання на чолі черги роботи відкидається, а потім виконання повторюється (який може потерпіти невдачу знову, в результаті чого це буде повторюватися.)
Ви можете реалізувати власну заводську нитку для наведених нижче випадків використання:
Це правильно, Executors.newCachedThreadPool()
не є чудовим вибором для коду сервера, який обслуговує декілька клієнтів і одночасні запити.
Чому? В основному це два (пов'язані) проблеми:
Це без обмежень, а це означає, що ви відкриваєте двері для того, щоб хтось калічив ваш JVM, просто вклавши в службу більше роботи (DoS-атака). Нитки споживають незначний об'єм пам’яті, а також збільшують споживання пам’яті залежно від їх незавершеної роботи, тому зрушити сервер досить легко таким чином (якщо у вас немає інших вимикачів).
Проблема без обмежень посилюється тим, що Виконавець стикається з тим, SynchronousQueue
що означає прямий хід передачі між завданням та пулом ниток. Кожне нове завдання створить новий потік, якщо всі існуючі потоки зайняті. Це, як правило, погана стратегія коду сервера. Коли процесор насичується, для завершення існуючих завдань потрібно більше часу. Ще більше завдань подається і створюється більше ниток, тому виконання завдань займає більше часу. Коли процесор насичений, більше потоків точно не те, що потрібно серверу.
Ось мої рекомендації:
Використовуйте пул потоків фіксованого розміру Executors.newFixedThreadPool або ThreadPoolExecutor. із заданою максимальною кількістю ниток;
ThreadPoolExecutor
Клас є базовою для реалізації виконавців, які повертаються з багатьох Executors
фабричних методів. Тож давайте підходимо до фіксованих та кешованих пулів ниток відThreadPoolExecutor
точки зору.
Головний конструктор цього класу виглядає наступним чином :
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
corePoolSize
Визначає мінімальний розмір пулу цільової нитки.Реалізація підтримує пул такого розміру, навіть якщо немає завдань для виконання.
maximumPoolSize
Максимальна кількість потоків , які можуть бути активними одночасно.
Після того, як пул потоків зростає і стає більшим за corePoolSize
поріг, виконавець може припинити непрацюючі нитки і дійти до corePoolSize
знову. Якщо allowCoreThreadTimeOut
це правда, то виконавець може навіть припинити основні потоки пулу, якщо вони простоювали більшеkeepAliveTime
поріг.
Отже, підсумковий рядок - якщо нитки залишаються в режимі очікування більше, ніж keepAliveTime
поріг, вони можуть припинятися, оскільки на них немає попиту.
Що відбувається, коли приходить нове завдання і всі основні потоки зайняті? Нові завдання будуть в черзі всередині цьогоBlockingQueue<Runnable>
екземпляра. Коли потік стає вільним, одна з цих завдань у черзі може бути оброблена.
У BlockingQueue
Java є різні реалізації інтерфейсу, тому ми можемо реалізувати різні підходи до черги, такі як:
Обмежена черга : нові завдання ставлять у чергу всередині обмеженої черги завдань.
Без обмеженої черги: нові завдання стоятимуть у черзі всередині необмеженої черги завдань. Тож ця черга може зростати стільки, скільки дозволяє розмір купи.
Синхронна передача даних : Ми також можемо використовувати SynchronousQueue
для встановлення черги нових завдань. У такому випадку, коли виводяться черги з новим завданням, на це завдання вже повинна чекати інша нитка.
Ось як ThreadPoolExecutor
виконується нове завдання:
corePoolSize
потоків, намагається запустити новий потік із заданим завданням як перше завдання.BlockingQueue#offer
методу. offer
Метод не блокуватиме , якщо чергу заповнена і негайно повертається false
.offer
повертається false
), то вона намагається додати нову нитку до пулу потоків, з цим завданням в якості свого першого завдання.RejectedExecutionHandler
.Основна відмінність між фіксованими та кешованими пулами ниток зводиться до цих трьох факторів:
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | Тип басейну | Розмір основної | Максимальний розмір | Стратегія черги | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Виправлено | n (фіксований) | n (фіксований) | Без обмеження `LinkedBlockingQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Кешований | 0 | Цілий.MAX_VALUE | `SynchronousQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
Excutors.newFixedThreadPool(n)
працює:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Як ви можете бачити:
OutOfMemoryError
.Коли я повинен використовувати те чи інше? Яка стратегія краща з точки зору використання ресурсів?
Пул потоків фіксованого розміру, здається, є хорошим кандидатом, коли ми збираємось обмежити кількість одночасних завдань для управління ресурсами .
Наприклад, якщо ми збираємось використовувати виконавця для обробки запитів веб-сервера, фіксований виконавець може обробляти запити, що порушуються, більш розумно.
Для ще кращого управління ресурсами настійно рекомендується створити користувацький ThreadPoolExecutor
з обмеженою BlockingQueue<T>
реалізацією, поєднаною з розумною RejectedExecutionHandler
.
Ось як Executors.newCachedThreadPool()
працює:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Як ви можете бачити:
Integer.MAX_VALUE
. Практично пул ниток не обмежений.SynchronousQueue
завжди не вдається, коли на іншому кінці немає кого прийняти!Коли я повинен використовувати те чи інше? Яка стратегія краща з точки зору використання ресурсів?
Використовуйте його, коли у вас багато передбачуваних короткочасних завдань.
Ви повинні використовувати newCchedThreadPool лише тоді, коли у вас є короткочасні асинхронні завдання, як зазначено в Javadoc, якщо ви подаєте завдання, для обробки яких потрібен довший час, ви в кінцевому підсумку створите занадто багато потоків. Ви можете потрапити на 100% процесора, якщо подавати тривалі завдання з швидшою швидкістю до newCchedThreadPool ( http://rashcoder.com/be-careful-time-using-executors-newcachedthreadpool/ ).
Я роблю кілька швидких тестів і маю такі результати:
1) якщо використовується SynchronousQueue:
Після досягнення ниток максимального розміру будь-яка нова робота буде відхилена за винятком, як показано нижче.
Виняток у потоці "main" java.util.concurrent.RejectedExecutionException: Завдання java.util.concurrent.FutureTask@3fee733d відхилено від java.util.concurrent.ThreadPoolExecutor@5acf9800 [Запуск, розмір пулу = 3, активні теми = queed 3 = 0, виконані завдання = 0]
на java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)
2) якщо використовується LinkedBlockingQueue:
Нитки ніколи не збільшуються від мінімального розміру до максимального розміру, тобто пул ниток має фіксований розмір як мінімальний розмір.