Здравствуйте, меня зовут почему.
Я уже писал несколько статей о пулах потоков, а затем мой однокурсник обошел круг и обнаружил, что я не написал статью о пулах потоков.@Async
Аннотированная статья, так что он пришел спросить меня:
Да, у меня разборки.
Причина, по которой мне не нравится эта аннотация, заключается в том, что я ее вообще не использовал.
Я привык использовать собственный пул потоков для выполнения некоторой асинхронной логики, и я использую его уже много лет.
Так что если это проект под моим руководством, то в проекте вы его точно не увидите@Async
Аннотированный.
Я видел это раньше@Async
Аннотировать?
Я, должно быть, видел это Некоторым друзьям нравится использовать эту аннотацию.
Асинхронную разработку можно сделать с помощью одной аннотации, как здорово.
Я не знаю, знают ли люди, которые используют эту аннотацию, как она работает, я в любом случае не знаю.
Во время недавней разработки был введен компонент, и было обнаружено, что эта аннотация использовалась в некоторых местах в вызываемом методе.
Поскольку он используется на этот раз, давайте изучим его.
Первое, что следует отметить, это то, что в этой статье не говорится о точках знаний, связанных с пулом потоков.
Просто опишите, как я познакомился с этой аннотацией, о которой раньше не знал.
Сделать демонстрацию
Я не знаю, что делать, если вы столкнулись с этой ситуацией.
Но я думаю, с какого бы угла вы ни начали, в конечном итоге он попадет в исходный код.
Итак, я обычно сначала выдвигаю Демо.
Демо очень простое, всего три класса.
Первый — это класс запуска, который мало что говорит:
Затем создайте службу:
Метод syncSay в этом сервисе помечен@Async
аннотация.
Наконец, сделайте контроллер, чтобы вызвать это, и вы закончите:
Демо готово, можете сделать и сами, это займет больше 5 минут, а я проиграю.
Затем запустите проект, вызовите интерфейс и просмотрите лог:
Я иду, судя по названию темы, это не асинхронно, да?
Почему это все еще нить кота?
Итак, на исследовательской дороге я столкнулся с первой проблемой:@Async
Аннотация не вступает в силу.
Почему это не работает?
Почему это не работает?
Я тоже ошарашен, я ничего не знал об этой аннотации до того, как я это сказал, так откуда я знаю?
Что вы будете делать, когда столкнетесь с этой проблемой?
Конечно, это для программирования браузера!
В этом месте, если я проанализирую исходный код, почему это не действует, я смогу выяснить причину.
Однако, если я программирую для браузера, это занимает всего 30 секунд, и я могу найти эти две части информации:
Причина отказа:
- 1.
@SpringBootApplication
Класс запуска не добавлен@EnableAsync
аннотация. - 2. Нет прокси-класса Spring. так как
@Transactional
и@Async
Реализация аннотаций основана на АОП Spring, а реализация АОП основана на режиме динамического прокси. Тогда причина отказа аннотации очевидна, может быть потому, что метод вызывается самим объектом, а не прокси-объектом, потому что он не управляется контейнером Spring.
Очевидно, моя ситуация соответствует первой ситуации, не добавляя@EnableAsync
аннотация.
Еще одна причина, мне это тоже очень интересно, но сейчас моя первая задача — собрать демо, так что не могу соблазниться другой информацией.
Когда многие студенты задают вопросы, первоначальный вопрос@Async
Почему аннотация не подействовала, а результат потихоньку пошел не так.Через пятнадцать минут проблема постепенно переросла в процесс запуска SpringBoot.
Еще через полчаса на веб-странице появятся такие вещи, как эссе о восьми ногах, которое нужно процитировать в интервью...
Когда я говорю это, я имею в виду, искать проблемы и искать проблемы. В процессе проверки задачи этот вопрос обязательно приведет к более интересным вопросам. Но, для протокола, не позволяйте проблеме расходиться.
Этот принцип такой же, как смотреть на исходный код с проблемой, глядя на него, вы можете даже не знать, в чем ваша проблема.
Хорошо, вернемся к этому.
Я добавляю этот комментарий к классу запуска:
Повторите вызов:
Вы можете видеть, что имя потока изменилось, что указывает на то, что это действительно хорошо.
Теперь, когда моя демонстрация настроена, я могу начать искать ракурсы для ее развертывания.
Из приведенного выше журнала я также могу узнать, что по умолчанию существует поток с префиксомtask-
Пул потоков помогает мне выполнять задачи.
Когда дело доходит до пулов потоков, я должен знать соответствующую конфигурацию этого пула потоков, чтобы чувствовать себя непринужденно.
Так откуда я могу знать?
нажмите сначала
На самом деле идея нормальных людей в это время должна состоять в том, чтобы просмотреть исходный код и найти соответствующее место для внедрения в пул потоков.
А мне, это немного ненормально, мне лень лезть в исходники, чтобы его найти, я хочу, чтобы мне его выставили.
Как сделать его открытым?
Опираясь на свое понимание пулов потоков, моей первой мыслью было сначала подавить этот пул потоков.
Если его раздавить, он не сможет справиться с задачей, и он уйдет в логику отказа, Обычно он выдает исключение, верно?
Итак, я немного модифицировал программу:
Чего я хочу, так это прийти прямо к волне великих чудес:
результат...
Оказалось...
Я получил все счета, есть какие-то отклонения?
Журнал печатает несколько строк в секунду, что очень радует:
Хотя ожидаемое мной исключение отклонения не появилось, я все же увидел небольшую подсказку в журнале.
Например, я обнаружил, что таких таксов может быть до 8:
Друзья, что вы хотите этим сказать?
Означает ли это, что основная конфигурация количества потоков для этого пула потоков, который я ищу, равна 8?
Что, вы спросите меня, почему это не может быть максимальное количество потоков?
Является ли это возможным?
Конечно, это возможно. Но когда я отправил 10 000 задач, политика отклонения пула потоков не сработала, поэтому я просто израсходовал максимальный пул потоков?
То есть конфигурация этого пула потоков имеет длину очереди 9992 и максимальное количество потоков 8?
Это слишком случайно и необоснованно, не так ли?
Поэтому я думаю, что конфигурация основного количества потоков равна 8, а длина очереди должна бытьInteger.MAX_VALUE
.
Чтобы подтвердить свою догадку, я изменил запрос на это:
число = 10 миллионов.
Наблюдайте за использованием памяти кучи через jconsole:
Это называется всплеском, и нажатие кнопки [Выполнить сборщик мусора] не дает никакого облегчения.
Он также доказан со стороны: задача может быть в очереди в очереди, вызывая парение памяти.
Хотя я еще не знаю, какова его конфигурация, но после проверки черного ящика у меня есть законные основания подозревать:
Пул потоков по умолчанию рискует вызвать переполнение памяти.
Однако это также означает, что я хочу бросить из него исключение, чтобы моя неприятная идея разоблачить себя передо мной провалилась.
исходный код
Предыдущие идеи не работают, поэтому давайте честно начнем бороться с исходным кодом.
Я начал с этого комментария:
Нажав на эту аннотацию, несколько абзацев на английском, не долго, я получил ключевую информацию из нее:
В основном сосредоточьтесь на том, где я провожу линию.
In terms of target method signatures, any parameter types are supported.
В сигнатуре целевого метода поддерживается любой тип входного параметра.
Еще одно слово: когда речь заходит о целевых методах и целях, в голове у всех сразу должно появиться понятие прокси-объекта.
Приведенное выше предложение легко понять и даже кажется ерундой.
Однако за ним следует Однако:
However, the return type is constrained to either void or Future.
Ограниченный, ограниченный, ограниченный.
Это предложение означает: тип возвращаемого значения ограничен значением void или Future.
Что ты имеешь в виду?
Так что, если я хочу вернуть строку?
WTF, распечатка на самом деле нулевая! ?
Итак, если я возвращаю объект, не легко ли получить исключение нулевого указателя?
Почитав комментарии к аннотациям, нашел вторую скрытую яму:
если
@Async
Для аннотированных методов возвращаемое значение может быть только void или Future.
Не будем говорить о пустоте, поговорим об этом Будущем.
Посмотрите на другое предложение, которое я подчеркнул:
it will have to return a temporary {@code Future} handle that just passes a value through: e.g. Spring's {@link AsyncResult}
На нем есть временное, которое является лексикой четвертого уровня, следует понимать, что оно означает краткосрочное и временное.
временный работник, временный работник, поймите.
Это означает, что если вы хотите вернуть значение, вы оборачиваете его в объект AsyncResult, который является временным исполнителем.
нравится:
Затем обращаем внимание на атрибут value аннотации:
Эта аннотация, глядя на значение аннотации выше, означает, что это должно заполнить имя бобов пула резьбы, что эквивалентно указать значение пула резьбы.
Не знаю, правильно я понял или нет, позже напишу метод для проверки.
Хорошо, пока я собрал воедино информацию.
- Раньше я вообще не понимал эту аннотацию. Теперь у меня есть Демо. Когда я построил Демо, я обнаружил, что кроме
@Async
В дополнение к аннотациям также необходимо добавить@EnableAsync
Аннотации, такие как добавление в класс запуска. - Затем я протестировал этот пул потоков по умолчанию как черный ящик, подозревая, что количество основных потоков по умолчанию равно 8, а длина очереди бесконечно длинна. Существует риск переполнения памяти.
- чтением
@Async
Я обнаружил, что возвращаемое значение может быть только типа void или Future, иначе, даже если будут возвращены другие значения, об ошибке не будет сообщено, но возвращаемое значение равно null, и есть риск нулевых указателей. -
@Async
В аннотации есть атрибут value, и должна быть возможность указать собственный пул потоков из аннотации.
Далее я ранжирую вопросы для изучения, сосредоточив внимание только на@Async
по сопутствующим вопросам:
- 1. Какова конкретная конфигурация пула потоков по умолчанию?
- 2. Как исходный код поддерживает только void и Future?
- 3. Для чего нужен атрибут value?
Какая конкретная конфигурация?
Я обнаружил, что конкретная конфигурация на самом деле является очень быстрым процессом.
Потому что параметр value этого класса просто слишком дружелюбен:
Есть пять вызовов, четыре из которых являются комментариями.
Действительный вызов находится в этом месте, просто сначала нажмите точку останова, а затем скажите:
org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor#getExecutorQualifier
После инициирования вызова он действительно побежал на точку останова:
Отладка вниз по точке останова, вы приедете в это место:
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor
Структура кода очень понятна.
Место под номером ① предназначено для получения соответствующего метода.@Async
Значение аннотации. Это значение на самом деле является именем бина, если оно не пустое, соответствующий бин будет получен из контейнера Spring.
Если значение не имеет значения, как в случае с нашей демонстрацией, оно переместится в место с номером ②.
Это место является пулом потоков по умолчанию, который я ищу.
Наконец, будь то пул потоков по умолчанию или наш собственный пул потоков в контейнере Spring.
Все принимают метод в качестве измерения и поддерживают отношение сопоставления между методом и пулом потоков на карте..
То есть шаг под номером ③, исполнители в коде — карта:
Итак, я ищу логику этого места под номером ②.
В основном есть объект defaultExecutor:
Эта вещь представляет собой функциональное программирование, поэтому, если вы не знаете, что она делает, отладка может немного запутать:
Я предлагаю вам пойти и добраться за это, вы можете начать за 10 минут.
В конце концов вы будете отлаживать это место:
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor
Этот код немного интересен, он предназначен для получения bean-компонента, связанного с пулом потоков по умолчанию, из BeanFactory. Процесс очень простой, лог тоже очень четко распечатан, так что не буду вдаваться в подробности.
Но интересный момент, который я хочу сказать, заключается в том, что я не знаю, видите ли вы этот код, видите ли вы какие-либо следы родительского делегирования.
Оба используют исключения и обрабатывают логику в исключениях.
Приведенный выше «мусорный» код напрямую нарушает две основные статьи в спецификации разработки Ali:
Это хороший код в исходном коде.
Внутри бизнес-процесса это нарушение нормы.
Итак, отступление.
Я лично считаю, что спецификация разработки Ali на самом деле является лучшей практикой для наших коллег, которые пишут бизнес-код.
Однако когда эта шкала будет распространена на промежуточное программное обеспечение, базовые компоненты и исходный код фреймворка, будет небольшая акклиматизация.Об этой вещи есть разные мнения.Я думаю, что Али разрабатывает стандартный плагин идеи.Для программистов , он действительно ароматный.
Без лишних слов вернемся и посмотрим на полученный пул потоков:
Разве это не то, что я хочу? Вы можете увидеть все соответствующие параметры этого пула потоков.
Это также подтвердило мою предыдущую гипотезу:
Я думаю, что конфигурация основного количества потоков равна 8, а длина очереди должна быть Integer.MAX_VALUE.
Однако теперь я получаю bean-компонент этого пула потоков непосредственно из BeanFactory, так когда же этот bean-компонент внедряется?
Друзья, неужели это легко?
У меня уже есть beanName этого bean-компонента, который является applicationTaskExecutor, Пока вы владеете текстом из восьми частей процесса получения bean-компонентов в Spring, вы все знаете, что в этом месте можно поставить точку останова, добавить отладку условия и медленно переходите к отладке.
org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)
Предположим, вы просто не знаете, как отлаживать точку останова выше?
Поговорим о простом и грубом способе: у вас уже есть beanName, вы не можете найти его в коде.
Просто и грубо работает хорошо:
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
Все нашли этот класс, просто попали в точку останова, можно приступать к отладке.
Давайте поговорим немного более неприятным.
Предположим, что сейчас я даже не знаю beaName, но знаю, что это должен быть пул потоков, управляемый Spring.
Затем я получу все пулы потоков, управляемые Spring в проекте.Должен быть тот, который я ищу, верно?
Посмотрите на скриншот ниже, разве текущий bean-компонент не является applicationTaskExecutor, который я ищу?
Это все дикие способы, подлые операции, просто знайте, иногда есть несколько идей для расследования.
поддержка возвращаемого типа
С первым вопросом о конфигурации мы закончили ранее.
Далее, давайте рассмотрим еще один ранее поднятый вопрос:
Как исходный код поддерживает только void и Future?
Ответ кроется в этом методе:
org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke
Место, обозначенное ①, на самом деле является методом взятия пула потоков, соответствующим методу из карты, которую мы анализировали ранее.
После получения пула потоков вы переходите к месту, обозначенному ②, которое должно инкапсулировать вызываемый объект.
Так что же инкапсулировано в объект Callable?
Этот вопрос не указан первым, давайте плотно обойдем нашу проблему, иначе проблем будет все больше и больше.
Место с пометкой ③, doSubmit, известно своим именем, и это место, где выполняется задание.
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#doSubmit
На самом деле вот ответ, который я ищу.
Вы можете видеть, что входным параметром returnType этого метода является String, который на самом деле является методом asyncSay, измененным аннотацией @Async.
Хотите верьте, хотите нет, но я могу показать вам предыдущий стек вызовов, где вы можете увидеть конкретный метод:
Ну, я тебе не врал.
Итак, теперь вы посмотрите, что делает метод doSubmit с возвращаемым типом этого метода.
Всего есть четыре ветви, и первые три определяют, является ли это типом Future.
Среди них ListenableFuture и CompletableFuture наследуются от Future.
Эти два класса также упоминаются в аннотации метода аннотации @Async:
И наша программа дошла до последнего else, а это значит, что возвращаемое значение не относится к типу Future.
Итак, что вы видите, что он делает?
После отправки задачи непосредственно в пул потоков возвращается нуль.
Это может быть не исключение нулевого указателя сломало его?
К этому месту мы также решили проблему:
Как исходный код поддерживает только void и Future?
На самом деле, причина очень проста: разве это не только эти два типа возврата, когда мы используем пул потоков для нормальной отправки?
Отправьте с помощью submit, верните Future и инкапсулируйте результат в Future:
Отправить с выполнением, без возвращаемого значения:
Фреймворк помогает нам достичь асинхронности с помощью простой аннотации, и он более причудлив, даже если он причудлив, он должен соответствовать основному принципу представления пула потоков.
Итак, почему исходный код поддерживает только возвращаемые типы void и Future?
Поскольку базовый пул потоков поддерживает только эти два типа возврата.
Просто его подход немного скомкан, а возвращаемые значения других возвращаемых типов напрямую обрабатываются как null.
Не обижайтесь, кто вам сказал не читать ноты на нотах.
Кроме того, я обнаружил, что в этом месте есть небольшая точка оптимизации:
Когда дело доходит до этого метода, возвращаемое значение явно равно нулю.
зачем использоватьexecutor.submit(task)
Отправить задачу?
Просто используйте выполнить.
Разница, вы спрашиваете меня о разнице?
Разве я только что не сказал, что метод submit имеет возвращаемое значение.
Хотя вам это и не нужно, он все равно создаст возвращаемый объект Future.
Тем не менее, он был построен, но не работал.
Поэтому я отправлю его непосредственно с выполнением.
Это оптимизация для создания на один объект Future меньше?
Можно сказать одно, это не ценная оптимизация, но говорят, что исходный код Spring был оптимизирован, и достаточно притворяться.
Далее поговорим о той части, которую мы не нажимали ранее, что именно инкапсулировано в месте под номером ②?
На самом деле, вы должны были догадаться об этом вопросе пальцами ног:
Просто причина, по которой я выкрутил его отдельно, заключается в том, что я хочу доказать вам, что результат, возвращаемый здесь, является реальным значением, возвращаемым нашим методом.
Я просто рассудил, что тип не Future и я его обрабатывать не буду, я, например, собственно сюда возвращаю.hi:1
Строка, только что не соответствующая условиям, выбрасывается:
Кроме того, идея все еще очень умна, она подскажет вам, что возвращаемое значение этого места проблематично:
Даже метод модификации отмечен для вас, вам нужно совсем немного, и он будет повторно изменен для вас.
Что касается того, почему это изменение необходимо, мы теперь очень ясно об этом.
Знай это и знай почему.
Значение аннотации @Async
Далее давайте посмотрим, что делает атрибут value аннотации @Async.
На самом деле, я уже упоминал об этом раньше, но я взял это с собой всего в одном предложении, а именно в этом месте:
Место под номером ①, упомянутое ранее, предназначено для получения соответствующего метода.@Async
Значение аннотации. Это значение на самом деле является именем бина, если оно не пустое, соответствующий бин будет получен из контейнера Spring.
Затем я непосредственно анализирую место, обозначенное ②.
Теперь мы снова смотрим на место, обозначенное ①.
Я также переделал тестовый пример, чтобы проверить свою идею.
В любом случае значение value должно быть именем bean-компонента Spring, и этот bean-компонент должен быть объектом пула потоков, что ни о чем не говорит.
Итак, я изменил демонстрационную программу, чтобы она выглядела так:
Запуск снова, переход к этой точке останова, просто отличной от нашей ситуации по умолчанию, на этот раз квалификатор имеет значение:
Следующим шагом является проникновение внутрь beanFactory, называемой WhyThreadPool компонента.
Наконец, извлеченный пул потоков — это пул потоков, который я настроил:
На самом деле это очень простой процесс исследования, но для этого есть причина.
Этот вопрос мне уже задавал одноклассник:
На самом деле эта проблема довольно показательна, многие студенты считают, что нельзя злоупотреблять пулом потоков и достаточно расшарить один для каждого проекта.
Нельзя злоупотреблять пулами потоков, но в проекте действительно может быть несколько пользовательских пулов потоков.
Разделите в соответствии с вашим бизнес-сценарием.
Например, в качестве простого примера можно использовать пул потоков в основном бизнес-процессе, но при возникновении проблемы в определенном звене основного процесса предполагается, что необходимо отправить сообщение раннего предупреждения.
Операция отправки сообщения раннего предупреждения может быть выполнена с помощью другого пула потоков.
Могут ли они совместно использовать пул потоков?
Да, его можно использовать.
Но что может пойти не так?
Допустим, в проекте есть проблема с бизнесом, и он постоянно и судорожно рассылает ранние предупреждающие текстовые сообщения, да еще и пул потоков переполнен.
В это время, если бизнес главного процесса и отправки текстовых сообщений используют один и тот же пул потока, какая красивая сцена появится?
Идет ли он непосредственно к политике отклонения, как только задача отправляется?
Вспомогательная функция отправки СМС раннего предупреждения привела к невозможности бизнеса Телега впереди лошади?
Поэтому рекомендуется использовать два разных пула потоков, каждый из которых выполняет свои функции.
На самом деле это технология изоляции пула потоков, которая звучит очень высоко.
затем упасть@Async
Что происходит в аннотациях?
На самом деле это так:
Затем, помните упомянутую ранее карту, которая поддерживает отношения отображения между методами и пулами потоков?
Это оно:
Теперь я запускаю программу и вызываю три вышеуказанных метода, цель которых — поместить значение в эту карту:
ты понимаешь?
Чтобы повторить это предложение еще раз:
Связь между методами и пулами потоков поддерживается в измерении метода.
Сейчас я@Async
Эта аннотация немного понимания, я думаю, что это все еще очень мило. Возможно, я рассмотрю возможность использования его в проекте позже. В конце концов, это больше соответствует философии программирования SpringBoot на основе аннотаций.
Последнее слово
Хорошо, я вижу это здесь, типа, следуй и упорядочивай любой, я не против, если устроишь их все. Написание статей утомительно и нуждается в небольшой положительной обратной связи.
Вот один для всех читателей и друзей: