newCachedThreadPool()
против newFixedThreadPool()
Когда я должен использовать один или другой? Какая стратегия лучше с точки зрения использования ресурсов?
newCachedThreadPool()
против newFixedThreadPool()
Когда я должен использовать один или другой? Какая стратегия лучше с точки зрения использования ресурсов?
Ответы:
Я думаю, что документы достаточно хорошо объясняют разницу и использование этих двух функций:
Создает пул потоков, который повторно использует фиксированное количество потоков, работающих в общей неограниченной очереди. В любой момент не более nThreads потоков будут активными задачами обработки. Если дополнительные задачи отправляются, когда все потоки активны, они будут ждать в очереди, пока поток не станет доступным. Если какой-либо поток завершается из-за сбоя во время выполнения до завершения работы, новый будет занимать его место, если это необходимо для выполнения последующих задач. Потоки в пуле будут существовать до тех пор, пока он не будет явно отключен.
Создает пул потоков, который создает новые потоки по мере необходимости, но будет повторно использовать ранее созданные потоки, когда они станут доступны. Эти пулы обычно улучшают производительность программ, которые выполняют много кратковременных асинхронных задач. Вызовы для выполнения будут повторно использовать ранее созданные потоки, если они доступны. Если существующий поток недоступен, новый поток будет создан и добавлен в пул. Потоки, которые не использовались в течение шестидесяти секунд, завершаются и удаляются из кэша. Таким образом, пул, который остается бездействующим достаточно долго, не будет потреблять никаких ресурсов. Обратите внимание, что пулы с похожими свойствами, но с разными деталями (например, параметры времени ожидания) могут быть созданы с помощью конструкторов ThreadPoolExecutor.
С точки зрения ресурсов, newFixedThreadPool
все потоки будут работать до тех пор, пока они не будут явно завершены. В newCachedThreadPool
потоках, которые не использовались в течение шестидесяти секунд, прекращаются и удаляются из кэша.
Учитывая это, потребление ресурсов будет очень сильно зависеть от ситуации. Например, если у вас огромное количество долгосрочных задач, я бы предложил FixedThreadPool
. Что касается CachedThreadPool
документов, они говорят, что «эти пулы обычно улучшают производительность программ, которые выполняют много кратковременных асинхронных задач».
newCachedThreadPool
может вызвать некоторые серьезные проблемы, потому что вы оставляете весь контроль над thread pool
и когда служба работает с другими на одном хосте , что может вызвать сбой других из-за долгого ожидания ЦП. Поэтому я думаю, что newFixedThreadPool
может быть более безопасным в такого рода сценарии. Также этот пост проясняет самые выдающиеся различия между ними.
Просто чтобы завершить остальные ответы, я хотел бы процитировать «Эффективная Java», 2-е издание, Джошуа Блоха, глава 10, пункт 68:
«Выбор службы executor для конкретного приложения может быть сложным. Если вы пишете небольшую программу или слегка загруженный сервер , использование 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>());
}
Если вас не волнует неограниченная очередь задач Callable / Runnable , вы можете использовать одну из них. Как предложил Брюно, я тоже предпочитаю , newFixedThreadPool
чтобы newCachedThreadPool
над этими двумя.
Но ThreadPoolExecutor обеспечивает более гибкие возможности по сравнению с любым newFixedThreadPool
илиnewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Преимущества:
У вас есть полный контроль над размером BlockingQueue . Это не безгранично, в отличие от двух предыдущих вариантов. Я не получу ошибку нехватки памяти из-за огромного скопления отложенных задач Callable / Runnable, когда в системе возникает непредвиденная турбулентность.
Вы можете реализовать собственную политику обработки отклонений ИЛИ использовать одну из политик:
По умолчанию ThreadPoolExecutor.AbortPolicy
обработчик генерирует исключительную ситуацию RejectedExecutionException при отклонении.
В ThreadPoolExecutor.CallerRunsPolicy
, поток, который вызывает выполнение, выполняет задачу. Это обеспечивает простой механизм управления с обратной связью, который замедляет скорость отправки новых задач.
В ThreadPoolExecutor.DiscardPolicy
, задача, которая не может быть выполнена, просто отбрасывается.
В ThreadPoolExecutor.DiscardOldestPolicy
, если исполнитель не выключается, задача во главе очереди работы отбрасывается, а затем выполнение повторяется (который может потерпеть неудачу снова, в результате чего это будет повторяться.)
Вы можете реализовать собственную фабрику потоков для следующих случаев использования:
Это верно, Executors.newCachedThreadPool()
не лучший выбор для серверного кода, который обслуживает несколько клиентов и одновременных запросов.
Зачем? Есть в основном две (связанные) проблемы с этим:
Он не ограничен, что означает, что вы открываете дверь для любого, кто может нанести вред вашей JVM, просто добавляя больше работы в службу (атака DoS). Потоки потребляют немалый объем памяти, а также увеличивают потребление памяти в зависимости от их незавершенной работы, поэтому таким способом довольно легко опустить сервер (если у вас нет других автоматических выключателей).
Неограниченная проблема усугубляется тем фактом, что Executor находится перед a, SynchronousQueue
что означает прямую передачу обслуживания между исполнителем задач и пулом потоков. Каждая новая задача создает новый поток, если все существующие потоки заняты. Как правило, это плохая стратегия для серверного кода. Когда процессор загружается, выполнение существующих задач занимает больше времени. Тем не менее, отправляется больше задач и создается больше потоков, поэтому выполнение задач занимает все больше времени. Когда процессор загружен, больше потоков - это совсем не то, что нужно серверу.
Вот мои рекомендации:
Используйте пул потоков фиксированного размера Executors.newFixedThreadPool или ThreadPoolExecutor. с заданным максимальным количеством потоков;
ThreadPoolExecutor
Класс является базовой для реализации исполнителей, которые возвращаются из многих Executors
фабричных методов. Итак, давайте подойдем к пулам потоков Fixed и CachedThreadPoolExecutor
с точки зрения России.
Главный конструктор этого класса выглядит следующим образом :
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 | 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
. Практически, пул потоков не ограничен.SynchronousQueue
всегда терпит неудачу, когда на другом конце нет никого, чтобы принять ее!Когда я должен использовать один или другой? Какая стратегия лучше с точки зрения использования ресурсов?
Используйте его, когда у вас много предсказуемых краткосрочных задач.
Вы должны использовать newCachedThreadPool только тогда, когда у вас есть недолговечные асинхронные задачи, как указано в Javadoc. Если вы отправляете задачи, обработка которых занимает больше времени, вы в конечном итоге создадите слишком много потоков. Вы можете использовать 100% ЦП, если отправляете долго выполняющиеся задачи с более высокой скоростью в newCachedThreadPool ( http://rashcoder.com/be-careful- while-using-executors-newcachedthreadpool/ ).
Я делаю несколько быстрых тестов и получаю следующие выводы:
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:
Потоки никогда не увеличиваются от минимального размера до максимального размера, что означает, что пул потоков имеет фиксированный размер в качестве минимального размера.