Tomcat 8 с CompletableFutures в Java 8

Я хочу распараллелить свое приложение. Я использую Tomcat8 для развертывания своего веб-приложения. Я использую настройки Tomcat по умолчанию (количество потоков HTTP-коннектора 200 и настройки JVM по умолчанию). Я хочу использовать CompletableFuture в Java для параллельного выполнения задачи. Пример. Если у меня есть 3 задачи TASK1, TASK2, TASK3, то вместо их последовательного выполнения я хочу выполнять каждую задачу в отдельных потоках, используя CompletableFuture, и объединять результаты. Мой вопрос: в любой момент времени Tomcat получает 200 запросов. Сколько потоков безопасно создавать в Executor? Если Executors.newFixedThreadPool(600), 600 — это хорошее число, потому что в любой момент времени я получаю 200 запросов и три параллельные задачи для выполнения, поэтому мне нужно минимум 600 потоков (теоретически). Я чувствую, что создание большего количества потоков может привести к снижению производительности.


person Nandeesh    schedule 14.03.2018    source источник


Ответы (2)


Сколько потоков вы можете создать, зависит от многих факторов, в основном от спецификаций машины и ОС.

В этом ответе говорится.

Это зависит от используемого процессора, операционной системы, выполняемых другими процессами, используемой версии Java и других факторов. Я видел, как сервер Windows имеет> 6500 потоков, прежде чем машина выйдет из строя.

Я лично использовал почти 1000 потоков, и производительность моей машины все еще была хорошей.

При использовании Executors.newFixedThreadPool(600) вы должны проанализировать, является ли этот тип исполнителя лучшим, который соответствует характеристикам и потребностям вашего приложения.

Здесь вы можете увидеть сравнение между FixedThreadPool и CachedThreadPool:

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

Если в постоянном пуле потоков (из 600) большая часть потоков будет бездействовать большую часть времени, вы можете использовать пул потоков chache, который будет создавать столько потоков, сколько необходимо, а затем будет поддерживать их в течение определенного времени или до тех пор, пока они продолжают использоваться. Вы, вероятно, получите преимущество от использования фиксированного пула потоков, если у вас есть 200 постоянно выполняющих 3 задачи.

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

С другой стороны, если большинство задач являются короткими задачами, вы можете использовать Executors.newWorkStealingPool(), это гарантирует, что ваши доступные ядра процессора всегда работают, устанавливая уровень параллелизма с помощью Runtime.getRuntime().availableProcessors(), если какой-то поток заканчивает свою работу, он может украсть задачи из другого очередь потоков.

Вы можете увидеть больше о ForkJoinPool и Executors.newWorkStealingPool() (примечание: newWorkStealingPool использует ForkJoinPool внутри):

Подробная разница между Java8 ForkJoinPool и Executors.newWorkStealingPool?

person Jose Da Silva    schedule 14.03.2018
comment
Спасибо @ Хосе Да Силва. Я буду использовать метод проб и ошибок, прежде чем перейти к фактическому количеству потоков. - person Nandeesh; 15.03.2018
comment
Пожалуйста, я думаю, что это лучшее, что вы можете сделать, и если вы найдете число или потоки, которые подходят вашему приложению, и хотите использовать кешированный пул потоков, вы также можете указать там максимальное количество потоков с помощью пользовательского фабрика ниток. - person Jose Da Silva; 15.03.2018

Ответ Хосе Да Силвы правильный и умный. Еще немного пояснений здесь.

Нет жесткого правила, которому нужно следовать. Как говорили другие, это зависит от многих факторов, таких как характер конкретных задач, их продолжительность, насколько загружены задачи ЦП по сравнению с тем, как часто задачи могут ожидать ресурсов, как работает планировщик потоков в вашей хост-ОС и вашей JVM. , характер вашего процессора, количество ядер в вашем процессоре и многое другое.

Имейте в виду, что расписание для threads не является бесплатным. При планировании потоков для моментов их выполнения возникают накладные расходы. Переключение между потоками, переключение контекста требует затрат. Hyper-threading – это аппаратная функция, позволяющая снизить затраты на переключение контекста, но даже затем переключение между более чем двумя потоками для одного ядра возвращается к полному переключению контекста.

Таким образом, наивно думать, что «чем больше, тем лучше» с нитями. Довольно часто вы можете обнаружить, что меньшее количество потоков более производительно, чем слишком много потоков со слишком большим количеством переключений контекста.

Вообще говоря, сотни активных потоков могут быть контрпродуктивными, если только эти потоки не проводят большую часть своего времени, ничего не делая. И помните, что ваше приложение (Tomcat + веб-приложения) — не единственный источник активных потоков на хост-компьютере. В ОС и других приложениях, вероятно, запущены десятки несколько активных потоков и несколько более загруженных потоков (локальный ввод-вывод, сеть и т. д.).

Например, если у вас есть 4-ядерный ЦП с включенной гиперпоточностью, это означает 8 логических ядер, поэтому вы можете ожидать, что для вашего выделенного компьютера Tomcat будет использоваться 5 или около того. Если ваши потоки были заняты (с интенсивным использованием ЦП) около трети времени, вы можете начать с пула потоков около 12-20. Если потоки заняты только менее 5-10% времени, то, возможно, пул из 100. Затем отслеживайте реальную производительность и смотрите, как она работает. Если все ядра работают со 100% загрузкой в ​​течение нескольких минут, возможно, вы используете чрезмерную подписку и, возможно, захотите уменьшить размер пула потоков.

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

Если у вас много потоков, и каждый из них очень занят ЦП, например, шифрованием или кодеком, тогда вам нужно, чтобы размер пула потоков был меньше количества физических ядер. . В приведенном выше примере ограничьте пул двумя или тремя из четырех физических ядер (8 логических ядер с поддержкой Hyper-Threading), оставив физические ядра открытыми для процессов ОС или других приложений. В самом деле, если у вас действительно есть такие задачи, интенсивно использующие ЦП, вы можете рассмотреть возможность отключения гиперпоточности на компьютерах развертывания. Пары логических ядер с поддержкой Hyper-Threading на физическое ядро ​​не выполняются одновременно, они переключаются туда-сюда с меньшими затратами на переключение контекста, но не с нулевыми затратами. Если ваши задачи чрезвычайно интенсивно используют ЦП (довольно редко в обычных бизнес-приложениях) без перерывов на ожидание ресурсов, то гиперпоточность может быть бесполезной.

Конечно, вы не можете точно знать числа, описанные выше, как того требуют ваши конкретные веб-приложения с вашим конкретным развертыванием. Метод проб и ошибок — единственный путь.

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

person Basil Bourque    schedule 14.03.2018
comment
Спасибо @Basil Bourque. Я буду использовать метод проб и ошибок, прежде чем перейти к фактическому количеству потоков. - person Nandeesh; 15.03.2018