FixedThreadPool против CachedThreadPool: меньшее из двух зол

97

У меня есть программа, которая порождает потоки (~ 5-150), которые выполняют кучу задач. Первоначально я использовал a, FixedThreadPoolпотому что этот аналогичный вопрос предполагал, что они лучше подходят для более длительных задач, и с моими очень ограниченными знаниями о многопоточности я считал средний срок службы потоков (несколько минут) « долгоживущим ».

Однако недавно я добавил возможность создавать дополнительные потоки, и при этом я превысил установленный мной предел потоков. В этом случае было бы лучше угадать и увеличить количество потоков, которое я могу разрешить, или переключиться на a, CachedThreadPoolчтобы у меня не было потраченных впустую потоков?

Предварительно попробовав их обоих, похоже , нет никакой разницы, поэтому я склоняюсь к тому, чтобы CachedThreadPoolпросто избежать потерь. Однако означает ли срок жизни потоков, что я должен вместо этого выбрать FixedThreadPoolи просто обработать неиспользуемые потоки? Из-за этого вопроса кажется, что лишние потоки не потрачены впустую, но я был бы признателен за разъяснения.

Даниэль
источник

Ответы:

112

CachedThreadPool - это именно то, что вы должны использовать в своей ситуации, поскольку нет никаких отрицательных последствий для использования его для длительно работающих потоков. Комментарий в java-документе о том, что CachedThreadPools подходят для коротких задач, просто предполагает, что они особенно подходят для таких случаев, а не о том, что они не могут или не должны использоваться для задач, связанных с длительными задачами.

Для дальнейшего уточнения, Executors.newCachedThreadPool и Executors.newFixedThreadPool поддерживаются одной и той же реализацией пула потоков (по крайней мере, в открытом JDK) только с разными параметрами. Отличия заключаются только в минимуме и максимуме потока, времени уничтожения потока и типе очереди.

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>());
}

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

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

Тревор Фриман
источник
2
«CachedThreadPool - это именно то, что вы должны использовать в своей ситуации, поскольку нет никаких отрицательных последствий для его использования для длительно работающих потоков». Не думаю, что согласен. CachedThreadPool динамически создает потоки без верхнего предела. Длительные задачи с большим количеством потоков потенциально могут потреблять все ресурсы. Кроме того, наличие большего количества потоков, чем идеально, может привести к потере слишком большого количества ресурсов на переключение контекста этих потоков. Хотя в конце ответа вы объяснили, что требуется настраиваемое регулирование, начало ответа немного вводит в заблуждение.
Nishit
1
Почему бы просто не создать ограниченное ThreadPoolExecutorподобное ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar
47

Оба FixedThreadPoolи CachedThreadPool- зло в сильно загруженных приложениях.

CachedThreadPool опаснее чем FixedThreadPool

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

  1. Неограниченный характер очереди задач: это может привести к нехватке памяти или высокой задержке
  2. Длительно работающие потоки приведут CachedThreadPoolк выходу из-под контроля при создании потока

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

  1. Установите очередь задач как ограниченную очередь, чтобы лучше контролировать
  2. Правильный RejectionHandler - ваш собственный RejectionHandler или обработчики по умолчанию, предоставляемые JDK
  3. Если вам нужно что-то сделать до / после завершения задачи, переопределите beforeExecute(Thread, Runnable)иafterExecute(Runnable, Throwable)
  4. Переопределить ThreadFactory , если требуется настройка потока
  5. Управляйте размером пула потоков динамически во время выполнения (связанный вопрос SE: динамический пул потоков )
Равиндра бабу
источник
Что, если кто-то решит использовать commonPool?
Crosk Cool
1
@Ravindra - Вы прекрасно объяснили недостатки CachedThreadPool и FixedThreadPool. Это показывает, что вы хорошо разбираетесь в пакете параллелизма.
Ayaskant
5

Итак, у меня есть программа, которая порождает потоки (~ 5–150), которые выполняют кучу задач.

Вы уверены, что понимаете, как потоки на самом деле обрабатываются вашей ОС и выбранным оборудованием? Как Java сопоставляет потоки с потоками ОС, как это сопоставляет потоки с потоками процессора и т. Д.? Я спрашиваю, потому что создание 150 потоков внутри ОДНОЙ JRE имеет смысл только в том случае, если у вас есть массивные ядра / потоки ЦП под ними, что, скорее всего, не так. В зависимости от используемой ОС и ОЗУ создание более n потоков может даже привести к завершению работы JRE из-за ошибок OOM. Таким образом, вы должны действительно различать потоки и работу, которую они должны выполнять, сколько работы вы даже можете обработать и т. Д.

И в этом проблема с CachedThreadPool: нет смысла ставить в очередь длительную работу в потоках, которые на самом деле не могут выполняться, потому что у вас есть только 2 ядра ЦП, способные обрабатывать эти потоки. Если у вас останется 150 запланированных потоков, вы можете создать много ненужных накладных расходов для планировщиков, используемых в Java и ОС для их одновременной обработки. Это просто невозможно, если у вас всего 2 ядра ЦП, если только ваши потоки не ждут ввода-вывода или чего-то подобного все время. Но даже в этом случае множество потоков создаст много операций ввода-вывода ...

И эта проблема не возникает с FixedThreadPool, созданным, например, с помощью 2 + n потоков, где n, конечно, разумно низкое, потому что с этим оборудованием и ресурсами ОС используются гораздо меньше накладных расходов для управления потоками, которые все равно не могут выполняться.

Торстен Шёнинг
источник
Иногда лучшего выбора нет, у вас может быть просто 1 ядро ​​ЦП, но если вы запускаете сервер, на котором каждый пользовательский запрос запускает поток для обработки запроса, другого разумного выбора не будет, особенно если вы планируете для масштабирования сервера по мере роста вашей пользовательской базы.
Мишель Файнштейн
@mFeinstein Как можно не иметь выбора, если есть возможность выбрать реализацию пула потоков? В вашем примере с одним ядром ЦП просто порождение большего количества потоков просто не имеет никакого смысла, это идеально подходит для моего примера с использованием FixedThreadPool. Это также легко масштабируется, сначала с одним или двумя рабочими потоками, а затем с 10 или 15 в зависимости от количества ядер.
Thorsten Schöning
2
Подавляющее большинство реализаций веб-серверов создают один новый поток для каждого нового HTTP-запроса ... Их не волнует, сколько фактических ядер имеет машина, это упрощает реализацию и упрощает масштабирование. Это относится ко многим другим проектам, в которых вы просто хотите один раз кодировать и развертывать, и вам не нужно перекомпилировать и повторно развертывать, если вы меняете компьютер, который может быть экземпляром облака.
Мишель Файнштейн
@mFeinstein Большинство веб-серверов используют пулы потоков для запросов сами по себе просто потому, что порождение потоков, которые не могут выполняться, не имеет смысла, или они используют циклы событий для соединений и впоследствии обрабатывают запросы в пулах или тому подобное. Кроме того, вы упускаете суть, а именно, что вопрос заключается в том, чтобы кто-то мог выбрать правильный пул потоков, и порождение потоков, которые все равно не могут выполняться, все еще не имеет смысла. FixedthreadPool, настроенный на разумное количество потоков на машину в зависимости от ядер, отлично масштабируется.
Thorsten Schöning
3
@ ThorstenSchöning, наличие 50 потоков, связанных с ЦП, на двухъядерном компьютере бесполезно. Наличие 50 потоков с привязкой к вводу-выводу на двухъядерном компьютере может быть очень полезным.
Paul Draper