Executors.newCchedThreadPool () проти Executors.newFixedThreadPool ()


Відповіді:


202

Я думаю, що документи досить добре пояснюють різницю та використання цих двох функцій:

newFixedThreadPool

Створює пул потоків, який повторно використовує фіксовану кількість потоків, що працюють поза спільною необмеженою чергою. У будь-якій точці, щонайбільше nThreads буде активним завданням обробки. Якщо додаткові завдання будуть подані, коли всі потоки активні, вони будуть чекати в черзі, поки нитка не буде доступною. Якщо будь-який потік припиняється через збій під час виконання перед відключенням, новий зайняє своє місце, якщо потрібно для виконання подальших завдань. Нитки в пулі існуватимуть до тих пір, поки воно не буде явно закрито.

newCachedThreadPool

Створює пул потоків, який створює нові потоки за потребою, але повторно використовує попередньо побудовані нитки, коли вони доступні. Ці пули, як правило, покращують продуктивність програм, які виконують багато короткочасних асинхронних завдань. Виклики для виконання повторно використовуватимуть раніше побудовані потоки, якщо вони доступні. Якщо немає наявного потоку, буде створено та додано нову нитку до пулу. Нитки, які не були використані шістдесят секунд, завершуються та видаляються з кеша. Таким чином, пул, який достатньо довго залишається бездіяльним, не буде споживати жодних ресурсів. Зауважте, що пули з подібними властивостями, але різні деталі (наприклад, параметри тайм-ауту) можуть бути створені за допомогою конструкторів ThreadPoolExecutor.

З точки зору ресурсів, newFixedThreadPoolволя підтримуватиме всі потоки до їх явного припинення. У newCachedThreadPoolпотоках, які не були використані шістдесят секунд, вони припиняються та видаляються з кеша.

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


1
так, я пройшов через документи .. проблема полягає в ... fixThreadPool викликає помилку в пам'яті @ 3 потоку .. де як cachedPool всередині створює лише одну нитку .. збільшуючи розмір купи, я отримую те саме продуктивність для обох .. що-небудь ще мені не вистачає !!
гакіш

1
Чи надаєте Ви Threadfactory для ThreadPool? Я здогадуюсь, що може зберігатися певний стан у нитках, які не збирають сміття. Якщо ні, можливо, ваша програма працює настільки близько до граничного розміру купи, що при створенні 3-х потоків це спричиняє OutOfMemory. Крім того, якщо cachedPool внутрішньо створює лише один потік, це можливо означає, що ваші завдання виконуються синхронізовано.
bruno conde

@brunoconde Так само, як зазначає @Louis F., це newCachedThreadPoolможе спричинити серйозні проблеми, оскільки ви залишаєте весь контроль thread poolі коли служба працює з іншими в тому ж хості , що може призвести до збоїв інших людей через тривалий час очікування процесора. Тому я думаю, що newFixedThreadPoolв такому сценарії можна бути більш безпечним. Також цей пост роз'яснює найвизначніші відмінності між ними.
Херен

75

Для завершення інших відповідей я хотів би процитувати Ефективну Java, 2-е видання, Джошуа Блох, глава 10, пункт 68:

"Вибір служби виконавця для конкретної програми може бути складним. Якщо ви пишете невелику програму або злегка завантажений сервер , використовуючи Executors.new- CachedThreadPool, як правило, хороший вибір , оскільки він не вимагає конфігурації. правильна річ ». Але кешований пул потоків - не вдалий вибір для сильно завантаженого сервера виробництва !

У кешуватися пулі потоків , представлені завдання не ставляться в черзі , але негайно передати його в руках нитки для виконання. Якщо потоки недоступні, створюється нова . Якщо сервер настільки сильно завантажений, що всі його процесори повністю використані, і більше завдань надійде, то більше ниток буде створено, що тільки погіршить питання.

Тому на високо завантаженому виробничому сервері вам набагато краще використовувати Executors.newFixedThreadPool , який дає вам пул із фіксованою кількістю потоків або безпосередньо використовуючи клас ThreadPoolExecutor для максимального контролю. "


15

Якщо ви подивитесь на вихідний код , ви побачите, вони викликають 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>());
}

1
Точно виконавець кешованої нитки з чіткою верхньою межею і, скажімо, 5-10 хвилин простоювання просто ідеально підходить для більшості випадків.
Агостон Хорват

12

Якщо вас не турбує безмежна черга завдань, що викликаються / Виконується , ви можете скористатися одним із них. Як запропонував бруно, я теж вважаю за краще newFixedThreadPoolце newCachedThreadPoolнад цими двома.

Але ThreadPoolExecutor надає більш гнучкі функції порівняно з будь-яким newFixedThreadPoolабоnewCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

Переваги:

  1. Ви повністю контролюєте розмір BlockingQueue . Це не без обмежень, на відміну від попередніх двох варіантів. Я не вийду з помилки пам’яті через величезну групу вирішених завдань, що викликаються / Виконується, коли в системі є несподівана турбулентність.

  2. Ви можете реалізувати власну політику обробки відхилень або використовувати одну з політик:

    1. За замовчуванням ThreadPoolExecutor.AbortPolicyобробник кидає програму RejectedExecutionException при відхиленні.

    2. У ThreadPoolExecutor.CallerRunsPolicyцьому потоці, що викликає виконання, виконується сама задача. Це забезпечує простий механізм контролю зворотного зв’язку, який уповільнить швидкість подання нових завдань.

    3. У ThreadPoolExecutor.DiscardPolicyзадачі, яку неможливо виконати, просто випадає.

    4. В ThreadPoolExecutor.DiscardOldestPolicy, якщо виконавець не вимикається, завдання на чолі черги роботи відкидається, а потім виконання повторюється (який може потерпіти невдачу знову, в результаті чого це буде повторюватися.)

  3. Ви можете реалізувати власну заводську нитку для наведених нижче випадків використання:

    1. Для встановлення більш описового імені потоку
    2. Щоб встановити статус демона потоку
    3. Щоб встановити пріоритет потоку

11

Це правильно, Executors.newCachedThreadPool()не є чудовим вибором для коду сервера, який обслуговує декілька клієнтів і одночасні запити.

Чому? В основному це два (пов'язані) проблеми:

  1. Це без обмежень, а це означає, що ви відкриваєте двері для того, щоб хтось калічив ваш JVM, просто вклавши в службу більше роботи (DoS-атака). Нитки споживають незначний об'єм пам’яті, а також збільшують споживання пам’яті залежно від їх незавершеної роботи, тому зрушити сервер досить легко таким чином (якщо у вас немає інших вимикачів).

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

Ось мої рекомендації:

Використовуйте пул потоків фіксованого розміру Executors.newFixedThreadPool або ThreadPoolExecutor. із заданою максимальною кількістю ниток;


6

ThreadPoolExecutorКлас є базовою для реалізації виконавців, які повертаються з багатьох Executorsфабричних методів. Тож давайте підходимо до фіксованих та кешованих пулів ниток відThreadPoolExecutor точки зору.

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> екземпляра. Коли потік стає вільним, одна з цих завдань у черзі може бути оброблена.

У BlockingQueueJava є різні реалізації інтерфейсу, тому ми можемо реалізувати різні підходи до черги, такі як:

  1. Обмежена черга : нові завдання ставлять у чергу всередині обмеженої черги завдань.

  2. Без обмеженої черги: нові завдання стоятимуть у черзі всередині необмеженої черги завдань. Тож ця черга може зростати стільки, скільки дозволяє розмір купи.

  3. Синхронна передача даних : Ми також можемо використовувати SynchronousQueueдля встановлення черги нових завдань. У такому випадку, коли виводяться черги з новим завданням, на це завдання вже повинна чекати інша нитка.

Подання роботи

Ось як ThreadPoolExecutorвиконується нове завдання:

  1. Якщо працює менше, ніж corePoolSizeпотоків, намагається запустити новий потік із заданим завданням як перше завдання.
  2. В іншому випадку він намагається застосувати нове завдання за допомогою BlockingQueue#offerметоду. offerМетод не блокуватиме , якщо чергу заповнена і негайно повертається false.
  3. Якщо вона не вдається встановити чергу з новим завданням (тобто offerповертається false), то вона намагається додати нову нитку до пулу потоків, з цим завданням в якості свого першого завдання.
  4. Якщо не вдалося додати нову нитку, то виконавець або закритий, або насичений. У будь-якому випадку нове завдання буде відхилено за допомогою наданого RejectedExecutionHandler.

Основна відмінність між фіксованими та кешованими пулами ниток зводиться до цих трьох факторів:

  1. Розмір базового басейну
  2. Максимальний розмір басейну
  3. Черга
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Тип басейну | Розмір основної | Максимальний розмір | Стратегія черги |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Виправлено | 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. Практично пул ниток не обмежений.
  • Якщо будь-який потік простоює більше 1 хвилини, він може припинитися. Таким чином басейн може скорочуватися, якщо нитки залишаються занадто сильно непрацюючими.
  • Якщо всі виділені потоки зайняті під час появи нового завдання, то він створює нову нитку, оскільки пропонувати нове завдання SynchronousQueueзавжди не вдається, коли на іншому кінці немає кого прийняти!

Коли я повинен використовувати те чи інше? Яка стратегія краща з точки зору використання ресурсів?

Використовуйте його, коли у вас багато передбачуваних короткочасних завдань.


5

Ви повинні використовувати newCchedThreadPool лише тоді, коли у вас є короткочасні асинхронні завдання, як зазначено в Javadoc, якщо ви подаєте завдання, для обробки яких потрібен довший час, ви в кінцевому підсумку створите занадто багато потоків. Ви можете потрапити на 100% процесора, якщо подавати тривалі завдання з швидшою швидкістю до newCchedThreadPool ( http://rashcoder.com/be-careful-time-using-executors-newcachedthreadpool/ ).


1

Я роблю кілька швидких тестів і маю такі результати:

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:

Нитки ніколи не збільшуються від мінімального розміру до максимального розміру, тобто пул ниток має фіксований розмір як мінімальний розмір.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.