Executors.newCachedThreadPool () против Executors.newFixedThreadPool ()

Ответы:

202

Я думаю, что документы достаточно хорошо объясняют разницу и использование этих двух функций:

newFixedThreadPool

Создает пул потоков, который повторно использует фиксированное количество потоков, работающих в общей неограниченной очереди. В любой момент не более nThreads потоков будут активными задачами обработки. Если дополнительные задачи отправляются, когда все потоки активны, они будут ждать в очереди, пока поток не станет доступным. Если какой-либо поток завершается из-за сбоя во время выполнения до завершения работы, новый будет занимать его место, если это необходимо для выполнения последующих задач. Потоки в пуле будут существовать до тех пор, пока он не будет явно отключен.

newCachedThreadPool

Создает пул потоков, который создает новые потоки по мере необходимости, но будет повторно использовать ранее созданные потоки, когда они станут доступны. Эти пулы обычно улучшают производительность программ, которые выполняют много кратковременных асинхронных задач. Вызовы для выполнения будут повторно использовать ранее созданные потоки, если они доступны. Если существующий поток недоступен, новый поток будет создан и добавлен в пул. Потоки, которые не использовались в течение шестидесяти секунд, завершаются и удаляются из кэша. Таким образом, пул, который остается бездействующим достаточно долго, не будет потреблять никаких ресурсов. Обратите внимание, что пулы с похожими свойствами, но с разными деталями (например, параметры времени ожидания) могут быть созданы с помощью конструкторов ThreadPoolExecutor.

С точки зрения ресурсов, newFixedThreadPoolвсе потоки будут работать до тех пор, пока они не будут явно завершены. В newCachedThreadPoolпотоках, которые не использовались в течение шестидесяти секунд, прекращаются и удаляются из кэша.

Учитывая это, потребление ресурсов будет очень сильно зависеть от ситуации. Например, если у вас огромное количество долгосрочных задач, я бы предложил FixedThreadPool. Что касается CachedThreadPoolдокументов, они говорят, что «эти пулы обычно улучшают производительность программ, которые выполняют много кратковременных асинхронных задач».

Бруно Конде
источник
1
да, я просмотрел документы ... проблема в том, что ... fixedThreadPool вызывает ошибку нехватки памяти @ 3 потока ... где, поскольку cachedPool внутренне создает только один поток ... при увеличении размера кучи я получаю то же самое производительность для обоих .. есть что-то еще, что я пропускаю !!
хакиш
1
Вы предоставляете какой-либо Threadfactory для ThreadPool? Я предполагаю, что это может быть сохранение некоторого состояния в потоках, которое не является сборщиком мусора. Если нет, возможно, ваша программа работает так близко к предельному размеру кучи, что при создании 3 потоков это вызывает OutOfMemory. Кроме того, если cachedPool внутренне создает только один поток, это означает, что ваши задачи выполняются синхронизированно.
Bruno Conde
@brunoconde Точно так же, как @Louis F. указывает, что это newCachedThreadPoolможет вызвать некоторые серьезные проблемы, потому что вы оставляете весь контроль над thread poolи когда служба работает с другими на одном хосте , что может вызвать сбой других из-за долгого ожидания ЦП. Поэтому я думаю, что newFixedThreadPoolможет быть более безопасным в такого рода сценарии. Также этот пост проясняет самые выдающиеся различия между ними.
Слушай
75

Просто чтобы завершить остальные ответы, я хотел бы процитировать «Эффективная Java», 2-е издание, Джошуа Блоха, глава 10, пункт 68:

«Выбор службы executor для конкретного приложения может быть сложным. Если вы пишете небольшую программу или слегка загруженный сервер , использование 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>());
}
krmanish007
источник
1
Точно, кешированный исполнитель потоков со здравым верхним пределом и, скажем, 5-10 минут простоя, просто идеально подходит для большинства случаев.
Агостон Хорват
12

Если вас не волнует неограниченная очередь задач Callable / Runnable , вы можете использовать одну из них. Как предложил Брюно, я тоже предпочитаю , newFixedThreadPoolчтобы newCachedThreadPoolнад этими двумя.

Но ThreadPoolExecutor обеспечивает более гибкие возможности по сравнению с любым newFixedThreadPoolилиnewCachedThreadPool

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

Преимущества:

  1. У вас есть полный контроль над размером BlockingQueue . Это не безгранично, в отличие от двух предыдущих вариантов. Я не получу ошибку нехватки памяти из-за огромного скопления отложенных задач Callable / Runnable, когда в системе возникает непредвиденная турбулентность.

  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. Неограниченная проблема усугубляется тем фактом, что Executor находится перед a, SynchronousQueueчто означает прямую передачу обслуживания между исполнителем задач и пулом потоков. Каждая новая задача создает новый поток, если все существующие потоки заняты. Как правило, это плохая стратегия для серверного кода. Когда процессор загружается, выполнение существующих задач занимает больше времени. Тем не менее, отправляется больше задач и создается больше потоков, поэтому выполнение задач занимает все больше времени. Когда процессор загружен, больше потоков - это совсем не то, что нужно серверу.

Вот мои рекомендации:

Используйте пул потоков фиксированного размера Executors.newFixedThreadPool или ThreadPoolExecutor. с заданным максимальным количеством потоков;

Прашант Гаутам
источник
6

ThreadPoolExecutorКласс является базовой для реализации исполнителей, которые возвращаются из многих Executorsфабричных методов. Итак, давайте подойдем к пулам потоков Fixed и CachedThreadPoolExecutor с точки зрения России.

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порогового значения, они могут быть прерваны, так как на них нет спроса.

Queuing

Что происходит, когда приходит новая задача и все основные потоки заняты? Новые задачи будут поставлены в очередь внутри этого BlockingQueue<Runnable>экземпляра. Когда поток становится свободным, одна из этих задач в очереди может быть обработана.

Существуют разные реализации BlockingQueueинтерфейса в Java, поэтому мы можем реализовать разные подходы к организации очередей, такие как:

  1. Ограниченная очередь . Новые задачи будут поставлены в очередь в ограниченной задаче.

  2. Неограниченная очередь : новые задачи будут помещаться в очередь в неограниченной очереди задач. Таким образом, эта очередь может вырасти настолько, насколько позволяет размер кучи.

  3. Синхронная передача обслуживания : мы также можем использовать SynchronousQueueдля постановки новых задач в очередь. В этом случае при постановке в очередь новой задачи другой поток уже должен ждать этой задачи.

Представление работы

Вот как ThreadPoolExecutorвыполняется новая задача:

  1. Если corePoolSizeзапущено меньше потоков, пытается запустить новый поток с заданной задачей в качестве первого задания.
  2. В противном случае он пытается поставить новую задачу в очередь, используя BlockingQueue#offerметод. offerМетод не будет блокировать , если очередь заполнена и немедленно возвращается false.
  3. Если ему не удается поставить в очередь новую задачу (т.е. выполнить offerвозврат false), он пытается добавить новый поток в пул потоков с этой задачей в качестве своей первой работы.
  4. Если не удается добавить новый поток, то исполнитель либо выключается, либо насыщается. В любом случае, новая задача будет отклонена с использованием предоставленной RejectedExecutionHandler.

Основное различие между фиксированным и кэшированным пулами потоков сводится к следующим трем факторам:

  1. Размер основного пула
  2. Максимальный размер пула
  3. Queuing
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Тип бассейна | Размер ядра | Максимальный размер | Стратегия очередей |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Исправлено | n (фиксированный) | n (фиксированный) | Неограниченный `LinkedBlockingQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Кэшированный | 0 | Integer.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

Вы должны использовать newCachedThreadPool только тогда, когда у вас есть недолговечные асинхронные задачи, как указано в Javadoc. Если вы отправляете задачи, обработка которых занимает больше времени, вы в конечном итоге создадите слишком много потоков. Вы можете использовать 100% ЦП, если отправляете долго выполняющиеся задачи с более высокой скоростью в newCachedThreadPool ( http://rashcoder.com/be-careful- while-using-executors-newcachedthreadpool/ ).

Дханарадж Дурайрай
источник
1

Я делаю несколько быстрых тестов и получаю следующие выводы:

1) при использовании SynchronousQueue:

После того, как потоки достигнут максимального размера, любая новая работа будет отклонена за исключением, как показано ниже.

Исключение в потоке "main" java.util.concurrent.RejectedExecutionException: Задача java.util.concurrent.FutureTask@3fee733d отклонено из java.util.concurrent.ThreadPoolExecutor@5acf9800 [Выполняется, размер пула = 3, активные потоки = 3, задачи с очередями = 0, выполненные задачи = 0]

в java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)

2) при использовании LinkedBlockingQueue:

Потоки никогда не увеличиваются от минимального размера до максимального размера, что означает, что пул потоков имеет фиксированный размер в качестве минимального размера.

Майк Лин
источник