Душеприказчики.newCachedThreadPool() против исполнителей.newFixedThreadPool()

newCachedThreadPool() и newFixedThreadPool()

когда я должен использовать один или другой? Какая стратегия лучше, с точки зрения использования ресурсов?

7 ответов


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

newFixedThreadPool

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

newCachedThreadPool

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

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

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


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

" выбор службы исполнителя для конкретного приложения может быть сложным. Если вы пишете небольшая программа или слегка загруженный сервер, используя исполнителей.new-CachedThreadPool является вообще хороший выбор, поскольку он не требует конфигурации и вообще " делает право вещь."Но кэшированный пул потоков-это не подходит на тяжело нагруженный производственный сервер!

на кэшированный пул потоков, отправленные задачи не ставятся в очередь но сразу же передали нити для исполнения. если нет доступных потоков, создается новый. Если сервер настолько загружен, что все его процессоры полностью используются, и поступает больше задач, будет создано больше потоков, которые будут только все ухудшит.

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


Если вы видите код в grepcode, вы увидите, что они вызывают 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>());
}

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

почему? Есть в основном две (связанные) проблемы с ним:

  1. это неограниченно, что означает, что вы открываете дверь для любого, чтобы искалечить вашу JVM, просто вводя больше работы в службу (DoS-атака). Потоки потребляют несущественный объем памяти, а также увеличивают потребление памяти на основе их работа в процессе, поэтому довольно легко свергнуть сервер таким образом (если у вас нет других выключателей на месте).

  2. неограниченная проблема усугубляется тем, что перед исполнителем стоит SynchronousQueue что означает, что существует прямая передача между задатчиком задач и пулом потоков. Каждая новая задача создаст новый поток, если все существующие потоки заняты. Обычно это плохая стратегия для серверного кода. Когда CPU насыщается, существующие задачи принимают дольше, чтобы закончить. Еще больше задач отправляются и больше потоков создаются, поэтому задачи занимают все больше и больше времени для завершения. Когда процессор насыщен, больше потоков определенно не то, что нужно серверу.

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

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


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

но ThreadPoolExecutor обеспечивает более гибкие функции по сравнению с обоими newFixedThreadPool или newCachedThreadPool

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

плюсы:

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

  2. вы можете реализовать custom неприятие обращения политика или используйте одну из политик:

    1. по умолчанию ThreadPoolExecutor.AbortPolicy проводник бросает выполнения RejectedExecutionException на отказ.

    2. на ThreadPoolExecutor.CallerRunsPolicy поток который вызывает execute сам запускает задачу. Это обеспечивает простой механизм управления обратной связью, который замедлит скорость отправки новых задач.

    3. на ThreadPoolExecutor.DiscardPolicy, задача, которая не может быть выполнена, просто отбрасывается.

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

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

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

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


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

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

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

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

на java.утиль.параллельный.ThreadPoolExecutor$AbortPolicy.rejectedExecution (ThreadPoolExecutor.java: 2047)

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

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