Вот почему 45-я оригинальная статья. Поговорите о различных стратегиях выполнения пула потоков и стратегиях отклонения потока, а также обсудите, как заставить пул потоков сначала использовать самый большой пул потоков, а затем помещать задачи в очередь.
прогулочная доска с полостью отходов
Привет всем, я почему, программа обезьяна Сычуань, Чэнду хороший человек.
Прежде всего, характеристики этого числа, прежде чем делиться технологией, коротко о жизни. Пусть температура изделия будет немного больше.
Фотография выше была сделана во время бега. Перед мероприятием команда мероприятия организовала информационную кампанию по сбору слогана для каждого километра дорожных знаков.
Мое сообщение было удачно выбрано:
Все знают, на чем вы настаиваете, но вы должны быть чисты в своем сердце.
Я говорю о марафоне, но я говорю о других вещах.
Я помню солнце в тот день, солнце было как огонь, и на дороге было очень мало тени. Горечь в том, что я тоже сообщил об ультрамарафоне (он ультрамарафон, но на самом деле это марафон 42 км в целом коне плюс последние 3 км чистого подъема)
Сколько там солнца, позвольте мне показать вам сравнение:
Жара была настолько невыносимой, что в моем сердце километрах в 30 появились два маленьких человечка:
Один сказал: я так устал, я больше не могу бегать, я ухожу на пенсию.
Один сказал: ладно, ладно, у меня тоже все хорошо, я ухожу с соревнований.
Я сказал: Ба, посмотри, чего вы двое не затеваете, позвольте мне довести вас до финиша
Итак, я наткнулся на знак, который я представил на 36 километрах, и был так счастлив, что остановился, чтобы сделать несколько снимков. Скажите себе: держись, когда не можешь удержаться.
Последние 3 километра подъема я не знаю, сколько раз мне было тесно. Когда я увидел издалека финишную арку, я вдруг подумал о фразе, которую понял, когда был в Дуньхуане: тяжелая работа, которую я отдаю себе, не тяжелая работа, а счастье.
Ну вернемся к статье.
Противоречивый пул потоков JDK
Давайте начнем с пула потоков JDK, чтобы открыть вопрос.
Все еще используйте мою предыдущую статью"Как установить параметры пула потоков? Мейтуан дал ответ, который шокировал интервьюера. 》Примеры вопросов в подразделе «Убеди волну первым»:
просить:Это пользовательский пул потоков. Предположим, что в данный момент трудоемких задач 100. Сколько потоков выполняется?
Правильный ответ был дан в предыдущей статье, поэтому я не буду повторяться здесь.
Но когда я проходил собеседование, я встретил много друзей, которые мало что знали о пуле потоков JDK.
И у большинства таких людей общая проблема, то есть если они сталкиваются с проблемой не очень знакомой, то догадываются.
Когда собеседники сталкивались с этим вопросом, они внешне улыбались, но на самом деле я уже разобрался в их внутренней деятельности:
МД, я никогда не задавал этот вопрос, но только что услышал от интервьюера, что количество основных потоков равно 10, а максимальное количество потоков равно 30. Прочитайте вопрос и знайте, что ответ либо 10, либо 30.
Вопросы с несколькими вариантами ответов, 50-процентная вероятность попадания, вы не рискуете?
Подождите, 30 — это максимальное количество потоков? максимум? Я чувствую, что это все.
Поэтому, немного подумав об этом, я посмотрел на себя и уверенно сказал:
Так что я тоже слегка улыбнулась и сказала ему: спустись и узнай больше, давай поговорим о других вещах.
Действительно, если вы вообще не понимаете правил работы пула потоков JDK,Интуитивно я тоже думаю, что так и должно быть, будь то ядро или максимальное количество потоков, когда приходит задача, сначала должны быть израсходованы доступные потоки в пуле потоков, а потом задача должна быть поставлена в очередь для очереди.
К сожалению, пул потоков JDK нелогичен.
Есть ли пул потоков, соответствующий нашей интуиции?
немного,Вы часто используете Tomcat, запущенный процесс пула потоков в нем заключается в том, чтобы сначала израсходовать максимальное количество потоков, а затем отправлять задачи в очередь.
Я проведу вас через анализ.
Пул потоков Tomcat
Сначала откройте файл Tomcat server.xml и посмотрите:
Знакомо, да? Кто из изучавших java web не настраивал этот файл? Кто настраивал этот файл, не обращая внимания на конфигурацию Executor?
Конкретные настраиваемые элементы можно посмотреть в официальной документации:
http://tomcat.apache.org/tomcat-9.0-doc/config/executor.html
В то же время я нашел китайское описание настраиваемого параметра следующим образом:
Обратите внимание, что первым параметром является className, буква c на картинке отсутствует.
Потом еще два параметра, которые не ввели, добавлю:
1.prestartminSpareThreads: логический тип, при запуске сервера создавать ли потоки с минимальным количеством простаивающих потоков (основные потоки), значение по умолчанию — false.
2. threadRenewalDelay: длинный тип, когда мы настраиваем ThreadLocalLeakPreventionListener, он будет отслеживать, останавливается ли запрос. После того, как поток будет остановлен, он будет перестроен, если это необходимо.Чтобы избежать множественных потоков, этот параметр может определять, создаются ли 2 потока одновременно. В случае отклонения поток не будет воссоздан. Значение по умолчанию — 1000 мс, отрицательное значение означает отсутствие обновления.
В основном мы фокусируемся на параметре className.Если он не настроен, реализация по умолчанию:
org.apache.catalina.core.StandardThreadExecutor
Давайте сначала интерпретируем этот метод (Обратите внимание, что номер версии исходного кода Tomcat в этой статье: 10.0.0-M4.):
org.apache.catalina.core.StandardThreadExecutor#startInternal
Со строк 123 по 130 здесь создается пул потоков Tomcat. Это очень важно. Позвольте мне интерпретировать это:
123 строки
taskqueue = new TaskQueue(maxQueueSize);
Создайте очередь TaskQueue, которая наследуется от LinkedBlockingQueue:
Стоит отметить комментарии к этой очереди:
В основном, чтобы сказать, что это очередь задач, специально разработанная для пулов потоков. При использовании с пулами потоков он отличается от обычных очередей.
Также передана длина очереди, которая по умолчанию равна Integer.MAX_VALUE:
124 строки
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
Создайте ThreadFactory, три входных параметра которого следующие:
namePrefix: префикс имени. Можно указать, что по умолчанию «tomcat-exec-».
daemon: следует ли запускать в режиме потока демона. Значение по умолчанию верно.
приоритет: приоритет потока. число от 1 до 10, по умолчанию 5.
125 строк
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
Создайте пул потоков и его шесть входных параметров:
Я не буду объяснять конкретное значение этого, это то же самое, что и пул потоков JDK.
Просто чтобы показать вам параметры по умолчанию.
Еще один момент, на который стоит обратить внимание, это то, что ThreadPoolExecuteor здесь — это Tomcat, а не JDK, хотя имя такое же.
Взгляните на комментарий Tomcat ThreadPoolExecuteor, в котором упоминаются два момента: один — общее количество представлений, а другой — политика отклонения. Оно будет упомянуто позже.
126 строк
executor.setThreadRenewalDelay(threadRenewalDelay);
Установите параметр threadRenewalDelay. Это не является предметом этой статьи, поэтому вы можете пока игнорировать его.
Строки 127 - 129
if (prestartminSpareThreads) {
executor.prestartAllCoreThreads();
}
Укажите, следует ли предварительно запускать все пулы основных потоков, этот параметр также упоминался в предыдущей статье.
Параметр prestartminSpareThreads по умолчанию имеет значение false. Но я думаю, что это место, которое вы установили в true, также несколько раз одним махом. Совершенно ненужный.
Зачем?
Поскольку этот метод был вызван при построении пула потоков в строке 125:
Как видно из исходного кода, независимо от того, какой конструктор пула потоков вы вызываете, будет вызываться метод prestartAllCoreThreads.
Итак, это небольшая ошибка в Tomcat? Возьмите свою клавиатуру и дайте ей пр.
130 строк
taskqueue.setParent(executor);
Эта строка кода очень важна. Без этой строки кода пул потоков Tomcat ведет себя так же, как пул потоков JDK.
В качестве примера возьмем следующую программу:
Пользовательский пул потоков может содержать до 150+300 задач.
Когда строка 24 закомментирована, работающий процесс пула потоков Tomcat совпадает с текущим процессом пула потоков JDK, а количество запущенных потоков будет составлять только 5 основных программ.
Когда строка 24 раскомментирована, пул потоков Tomcat всегда будет создавать количество потоков до 150, а затем отправлять оставшиеся задачи в настраиваемую очередь TaskQueue.
Я предоставлю другую версию для копирования и вставки, которая запускается напрямую. Вы можете запустить ее отдельно, попробовать и посмотреть результат:
public class TomcatThreadPoolExecutorTest {скопировать кодpublic static void main(String[] args) throws InterruptedException { String namePrefix = "why不止技术-exec-"; boolean daemon = true; TaskQueue taskqueue = new TaskQueue(300); TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, Thread.NORM_PRIORITY); ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 150, 60000, TimeUnit.MILLISECONDS, taskqueue, tf); //taskqueue.setParent(executor); for (int i = 0; i < 300; i++) { try { executor.execute(() -> { logStatus(executor, "创建任务"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } } Thread.currentThread().join(); } private static void logStatus(ThreadPoolExecutor executor, String name) { TaskQueue queue = (TaskQueue) executor.getQueue(); System.out.println(Thread.currentThread().getName() + "-" + name + "-:" + "核心线程数:" + executor.getCorePoolSize() + "\t活动线程数:" + executor.getActiveCount() + "\t最大线程数:" + executor.getMaximumPoolSize() + "\t总任务数:" + executor.getTaskCount() + "\t当前排队线程数:" + queue.size() + "\t队列剩余大小:" + queue.remainingCapacity()); }
}
Затем проанализируйте назначение этой строки кода и посмотрите, как эта строка кода обращает вспять текущий процесс пула потоков JDK.
Никаких секретов под исходным кодом
Если вы немного знакомы с исходным кодом пула потоков JDK, вы, вероятно, можете догадаться, что Tomcat должен был что-то сделать для управления новым потоком, который находится в следующем месте:
PS: Следует отметить, что приведенный выше снимок экрана — это метод выполнения пула потоков JDK. Потому что отправка пула потоков Tomcat также является методом повторного использования. Но workQueue — это не та же самая очередь.
Тогда вы сначала хорошо разбираетесь в рабочем процессе и различных параметрах, потом пишете демо, а потом переходите к сумасшедшей отладке. Тогда ты всегда найдешь это место, и ты найдешь, его нетрудно найти.
Что ж, вышеизложенное в основном касается той части, которую я обвел.
В строке 1371 скриншота, если задача не будет успешно помещена в очередь (при условии, что пул потоков запущен), будет выполняться логика строки 1378, и эта логика является логикой создания непрофильных потоков.
Итак, после приведенного выше вывода все ясно,Tomcat просто нужно поднять шум в методе offer пользовательской очереди.
Итак, давайте сосредоточимся на этом методе:
org.apache.tomcat.util.threads.TaskQueue#offer
Чтобы более интуитивно увидеть, что она работает гладко, я поставил точку останова в строке 80, чтобы запустить программу следующим образом:
Вы можете увидеть несколько параметров внутри, и в следующем объяснении будут использоваться параметры внутри:
Первое, если суждение
Во-первых, первое if, чтобы определить, пуст ли родитель:
Как видно из снимка экрана с рабочими параметрами точки останова, родителем здесь является класс ThreadPoolExecutor Tomcat.
Когда parent имеет значение null, исходный метод предложения вызывается напрямую.
Итак, помните, что я сказал ранее?
Теперь вы знаете, почему?
Исходный код есть исходный код. Причина, это правда.
Следовательно, это не пусто, условие не выполнено, а следующее, если вводится суждение.
Второй, если суждение
Прежде всего, должно быть ясно, что, когда может быть введено второе суждение, количество запущенных в данный момент потоков должно быть больше или равно количеству основных потоков (поскольку логика, помещенная в очередь, уже выполняется, что указывает на что количество основных потоков должно быть заполнено), меньше, чем максимальное количество потоков.
Метод getPoolSize должен получить количество потоков, работающих в настоящее время в пуле потоков:
Поэтому второй if определяет, равно ли количество запущенных потоков максимальному количеству потоков. Если он равен, значит, все потоки работают, и задача кидается в очередь.
Как видно из скриншота параметров работы точки останова, текущий рабочий номер равен 5, а максимальное количество потоков равно 150. Если условие не выполняется, переходите к следующему решению.
Третий, если суждение
Во-первых, давайте посмотрим, что получает getSubmittedCount:
getSubmittedCount получает количество задач, которые были отправлены, но еще не завершены, и его значение равно числу в очереди плюс количество запущенных задач.
Как видно из скриншота параметров работы точки останова, текущие данные равны 6.
А parent.getPoolSize() равно 5.
Если условие не выполняется, переходите к следующему решению.
Но здесь нужно сказать, что если количество задач, которые были отправлены, но не завершены, меньше, чем количество запущенных потоков в пуле потоков, подход Tomcat состоит в том, чтобы поместить задачи в очередь, а не выполнять их немедленно.
На самом деле, это очень логичный и простой способ думать об этом.
Так или иначе, есть простаивающие потоки, и если их кинуть в очередь, они будут потребляться бездействующими потоками. Почему это нужно делать сейчас? Не говоря уже о процессе уничтожения, требуется дополнительная реализация.
Усилия не благодарны. Не обязательно.
Четвертый, если суждение
Это суждение имеет решающее значение.
Возвращает false, если количество запущенных в данный момент потоков меньше максимального количества потоков.
Обратите внимание, что все предыдущие решения if помещаются в очередь, если условия не выполняются. А вот если условие не выполняется, возвращает false.
Что означает возврат false?
Это означает выполнение 1378 строк кода для создания потоков.
Итак, вся блок-схема, вероятно, выглядит так:
Давайте снова поговорим о стратегии отказа
Политика отклонения должна учитывать этот метод:
org.apache.tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable, long, java.util.concurrent.TimeUnit)
Взгляните на комментарии к этому методу:
Если очередь заполнена, он будет ждать, пока указанное время не будет помещено в очередь снова.
Если он все еще полон, когда он снова помещается в очередь, выдается исключение отклонения.
Эта логика аналогична ситуации, когда вы идете в туалет и обнаруживаете, что все ямы заняты. В это время ваше тело говорит вам, что ваши мышцы сфинктера могут продержаться максимум минуту.
Итак, вы держали часы у двери, глубоко вздохнули, закрыли глаза, помедитировали и подождали минуту.
Если повезет, давайте еще раз взглянем: эй, есть пустая яма, поторопитесь и займите ее.
Если вам не повезло, давайте еще раз посмотрим: Эй, все еще нет места, что мне делать? Сбросить исключение. Я не буду говорить, как его выбросить, просто представьте себе.
Итак, давайте посмотрим на это место, как реализован код Tomcat:
Часть catch сначала определяет, является ли очередь пользовательской очередью Tomcat. Если да, введите эту ветку if.
Ключевая логика в этом, если суждение.
Вы можете увидеть строку 172:
if (!queue.force(command, timeout, unit))
Вызывается принудительный метод очереди. Мы знаем, что у BlockingQueue нет принудительного метода.
Таким образом, эта сила является настраиваемым методом очереди Tomcat:
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if (parent == null || parent.isShutdown())
throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
//forces the item onto the queue, to be used if the task is rejected
return super.offer(o,timeout,unit);
}
Зайдите, посмотрите и обнаружите: Вред, это просто так. Это просто оболочка для нативного метода offer.
Если вы успешно встанете в очередь, все будет хорошо, ничего не произойдет.
Если очередь не добавлена успешно, генерируется исключение, а параметр submitCount сохраняется.
Как было сказано ранее: параметр submitCount = количество задач в очереди + количество запущенных задач.
Поэтому здесь требуется операция вычитания.
Вот и все, что касается стратегии отказа.
Но исходный код этого места - это то, что я привел вас найти. Что делать, если вы хотите узнать для себя?
Вы думаете, что хотите проверить стратегию отказа. Это просто должно вызвать его политику отклонения.
Например следующее:
Отправьте 500 задач в пул потоков, который может содержать только 450 задач.
Затем будет выбрано это исключение:
Вы найдете строку 174 пула потоков Tomcat:
Затем вы нажимаете точку останова и начинаете играть.
Тогда то, что мы только что сказали, можешь подождать у двери минутку, прежде чем войти в яму?
Мы можем просто сообщить пулу потоков параметры, например следующие:
Затем переходите к выполнению, потому что при заполнении очереди срабатывает исключение отклонения, а затем подождите 3 секунды, прежде чем отправлять задачу. И поставленная нами задача может быть выполнена за 2 секунды.
Поэтому в этом сценарии все задачи будут выполняться нормально.
Теперь вы знаете, как усердно должен работать Tomcat, чтобы выполнять задачи, которые вы ему даете, как можно быстрее и в полном объеме?
пасхальные яйца
При просмотре пользовательской очереди Tomcat я нашел комментарий автора:
Результатом этого места является установка для параметра ForcedRemainingCapacity значения 0.
Когда был установлен этот параметр?
Вот когда происходит следующий метод выключения:
org.apache.tomcat.util.threads.ThreadPoolExecutor#contextStopping
Как видите, автор напрямую устанавливает для параметра forceRemainingCapacity значение 0 перед вызовом метода setCorePoolSize.
Причина приведенного выше комментария заключается в том, что метод JDK ThreadPoolExecutor.setCorePoolSize проверит, равно ли 0 оставшейся емкости.
Насчет того, почему делается такая проверка, автор Tomcat сказал дважды:Я не понимаю, почему. Я не понимал почему.
Итак, он подделал условие.
Короче говоря, он сказал, что не понимает, почему метод setCorePoolSize пула потоков JDK должен ограничивать оставшуюся длину очереди до 0 при настройке пула основных потоков.В любом случае, это правильный способ написать это.
Не спрашивай, спрашивай - это правило.
Поэтому я посмотрел на метод setCorePoolSize пула потоков JDK и обнаружил, что это ограничение было в jdk 1.6. Пул потоков был подвергнут крупномасштабному рефакторингу в версиях после 1.6, чтобы снять это ограничение:
Итак, какие проблемы вызовет Tomcat, непосредственно установивший значение 0?
Нормальная логика такова, что оставшийся размер очереди = длина очереди - количество задач в очереди в очереди.
И когда вы отслеживаете его пул потоков (длина очереди 300), нормальная ситуация должна быть такой:
Но при вызове метода contextStopping может возникнуть следующая проблема:
Очевидно, что это не соответствует приведенному выше алгоритму.
Ну, а если вам нужно в будущем следить за пулом потоков Tomcat, а версия JDK выше версии 1.6. Затем вы можете снять это ограничение, чтобы избежать ложных срабатываний.
Что ж, поздравляю, друг. Я выучил еще одну точку знаний, которая в принципе бесполезна, а количество странных знаний немного увеличилось.
Пул потоков Dubbo
Вот еще одно расширение реализации пула потоков Dubbo.
org.apache.dubbo.common.threadpool.support.eager.EagerThreadPoolExecutor
Вы можете посмотреть на это, мысль остается этой мыслью:
Но метод execute немного отличается:
С точки зрения кода, метод offer вызывается сразу после того, как здесь не удалось вставить значение, и нет времени ожидания.
То есть интервал между двумя предложениями очень короткий.
Вообще-то не совсем понимаю, зачем так написал.Может автор оставил дырку для расширения?
Потому что, если это написано так, почему бы просто не вызвать этот метод напрямую?
java.util.concurrent.LinkedBlockingQueue#offer(E)
Это также автор, который хочет сыграть в азартные игры за очень короткий промежуток времени, верно? Кто знает?
Затем вы можете обнаружить, что пул потоков также много сделал для стратегии отклонения:
Вы можете видеть, что печать журнала очень подробная, уровень предупреждений:
Метод dumpJStack, из названия видно, что он собирается в поток Dump, сохраните сцену:
В этом методе он использует пул потоков JDK по умолчанию для создания дампа потока исключений.
Подождите, разве в спецификации разработки Ali не говорилось, что не рекомендуется использовать пул потоков по умолчанию?
Фактически, эта спецификация зависит от того, как вы с ней справляетесь. В этом сценарии встроенный пул потоков может удовлетворить потребности.
А вы посмотрите на второе красное поле: после отправки выполняется метод shutdown, а выше дано глубокомысленное предупреждение.
Пул потоков должен быть закрыт, иначе это приведет к OOM.
Это подробности, друзья. Дьявол кроется в деталях!
Почему здесь используется shutdown, а не shutdownNow? В чем их отличие? Почему OOM не вызывает метод выключения?
Очки знаний, друзья, все очки знаний!
Что ж, на этом публикация этой статьи подошла к концу.
В дальнейшем, когда интервьюер будет спрашивать вас о запущенном процессе пула потоков JDK, после вашего ответа стиль рисования будет меняться, а потом еще один:
На самом деле, мы также можем сначала израсходовать максимальное количество потоков, а затем пустить задачу в очередь. Этого можно добиться, настроив очередь и переопределив ее метод предложения. В настоящее время известные мне Tomcat и Dubbo предоставляют пулы потоков с такой стратегией.
Между занижением и другим притворялась красивая сила. Позвольте интервьюеру перейти к следующему пункту знаний, чтобы вы могли показать больше себя.
Последнее слово (пожалуйста, обратите внимание)
В этой статье в основном представлен работающий процесс пула потоков Tomcat, который действительно отличается от процесса пула потоков JDK.
И почему пул потоков Tomcat делает это?
На самом деле это потому, что Tomcat обрабатывает большинство задач с интенсивным вводом-выводом, а пользователь ждет ответа впереди.В результате вы можете обработать запрос пользователя, но позволить запросу пользователя стоять в очереди и ждать?
Это не хорошо, не хорошо.
В конце концов, мы возвращаемся к вопросу о том, является ли тип задачи интенсивно использующим операции ввода-вывода или ЦП.
Если вам интересно, вы можете ознакомиться с этой моей статьей:"Как установить параметры пула потоков? Мейтуан дал ответ, который шокировал интервьюера. 》
Поставьте лайк, Чжоу Гэн очень устал, не разводите меня зря, вам нужен положительный отзыв.
Спасибо за прочтение, настаиваю на оригинальности, очень приветствую и благодарю за внимание.
Я почему технология, хороший сычуаньский человек, который не большой парень, но любит делиться, теплый и информативный.
Добро пожаловать в публичный аккаунт [почему больше, чем технологии] и настаивайте на выводе оригинальности. Делитесь технологиями, пробуйте жизнь, и я надеюсь, что вы и я вместе добьемся прогресса.