Собственный пул потоков такой мощный, зачем Tomcat расширять пул потоков?

Java

предисловие

Tomcat/Jetty в настоящее время является самым популярным веб-контейнером.После того, как оба принимают запрос, они будут переданы в пул потоков для обработки, что может эффективно повысить производительность обработки и параллелизм. JDK улучшает реализацию полного пула потоков, но ни Tomcat, ни Jetty не используют его напрямую. Jetty принимает решение собственной разработки и внедряет его внутри компанииQueuedThreadPoolКомпонент пула потоков, в то время как Tomcat использует схему расширения, опираясь на пул потоков JDK и расширяя собственный пул потоков JDK.

Можно сказать, что собственный пул потоков JDK относительно полнофункционален и относительно прост в использовании.Так почему же Tomcat/Jetty не выбрали это решение, а реализовали его самостоятельно?

Пул потоков JDK

Обычно мы можем разделить выполняемые задачи на две категории:

  • ресурсоемкие задачи
  • io интенсивные задачи

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

Для задач с интенсивным вводом-выводом, поскольку потоки не выполняются все время, они могут тратить большую часть своего времени на ожидание ввода-вывода для чтения/записи данных.Увеличение количества потоков может улучшить параллелизм и обработать как можно больше задач.

Рабочий процесс собственного пула потоков JDK выглядит следующим образом:

Подробности можно посмотретьВ этой статье рассказывается, как безопасно закрыть пул потоков., приведенный выше рисунок предполагает использованиеLinkedBlockingQueue.

Душевная пытка: вы пропустили описанный выше процесс? Долгое время я думал, что количество потоков достигло максимального количества потоков перед помещением в очередь.  ̄□ ̄||

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

Эта стратегия, очевидно, больше подходит для работы сcpuинтенсивные задачи, но дляioИнтенсивные задачи, такие как запросы к базе данных, вызовы запросов rpc и т. д., не очень дружелюбны.

Поскольку Tomcat/Jetty необходимо обрабатывать большое количество задач клиентских запросов, если используется собственный пул потоков, как только количество принятых запросов превышает количество основных потоков в пуле потоков, эти запросы будут помещены в очередь. и дождитесь обработки основного потока. Очевидно, что это снижает общую скорость обработки этих запросов, поэтому ни один из них не использует собственный пул потоков JDK.

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

Далее следует в основном расширение пула потоков с помощью Tomcat и обсуждение принципа его реализации.

Расширить пул потоков

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

Видно, что процесс пула потоков в основном делится на три этапа, второй этап основан наqueue#offerМетод возвращает результат, чтобы определить, нужно ли создавать новый поток.

Собственный тип очереди JDKLinkedBlockingQueue, SynchronousQueue, логика реализации у них разная.

LinkedBlockingQueue

offerВнутри метод будет судить, заполнена ли очередь или нет. Если очередь заполнена, вернутьсяfalse, если очередь не заполнена, добавить задачу в очередь и вернутьсяtrue.

SynchronousQueue

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

дляofferметод, если другие потоки блокируются методом выборки, метод вернетtrue. В противном случае метод предложения вернет false.

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

Конечно, приведенная выше логика реализации довольно грубая, давайте проверим логику ее реализации из исходного кода Tomcat.

Пул потоков расширения Tomcat

Пул потоков расширения Tomcat напрямую наследует пул потоков JDKjava.util.concurrent.ThreadPoolExecutor, чтобы переопределить логику некоторых методов. Также реализованоTaskQueue, прямое наследованиеLinkedBlockingQueue, переписатьofferметод.

Сначала посмотрите на использование пула потоков Tomcat.

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

Далее давайте рассмотрим основные методы пула потоков Tomcat.executeлогика.

executeЛогика метода относительно проста, и ядро ​​задачи по-прежнему передается для обработки собственному пулу потоков Java. Здесь мы в основном добавляем стратегию повторных попыток.Если собственный пул потоков выполняет стратегию отказа, бросаетRejectedExecutionExceptionаномальный. Это зафиксирует, а затем снова попытается добавить задачу вTaskQueue, выполнить задание как можно лучше.

Обратите внимание здесьsubmittedCountПеременная. Это важный параметр в пуле потоков Tomcat.AtomicIntegerпеременная, которая будет подсчитывать в реальном времени задачи, отправленные в пул потоков, но еще не выполненные. то естьsubmittedCountРавняется количеству задач в очереди пула потоков плюс задачи, выполняемые рабочими потоками пула потоков.TaskQueue#offerС помощью этого параметра будет реализована соответствующая логика.

Тогда мы в основном рассматриваемTaskQueue#offerлогика метода.

Основная логика заключается в третьем шаге, здесь, еслиsubmittedCountМеньше, чем количество потоков в текущем пуле потоков, будет возвращеноfalse. Выше мы упоминалиofferметод возвращаетfalse, пул потоков будет создавать новые потоки напрямую.

Добавлена ​​версия Dubbo 2.6.XEagerThreadPool, принцип его реализации аналогичен пулу потоков Tomcat, и заинтересованные партнеры могут его прочитать самостоятельно.

компромиссный подход

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

new ThreadPoolExecutor(10, 10,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(100));

Однако использование этого метода сделаетkeepAliveTimeСбой, после создания потока он всегда будет существовать, что является пустой тратой системных ресурсов.

Суммировать

JDK относительно хорошо реализует функции пула потоков, но он больше подходит для выполнения задач с интенсивным использованием ЦП, а не задач с интенсивным вводом-выводом. Для задач с интенсивным вводом-выводом это можно сделать косвенно, установив параметры пула потоков.

Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: программа для общения, ежедневный толчок галантерейных товаров. Если вас интересует мой рекомендуемый контент, вы также можете подписаться на мой блог:studyidea.cn

其他平台.png