Откуда берутся потоки в Spring Boot приложении и кто их предоставляет
Что проверяют
Знаете ли вы, что Spring сам потоков почти не создаёт: он берёт их у контейнера сервлетов и у пулов, объявленных в конфигурации. Вопрос отделяет тех, кто ставил @Async по образцу, от тех, кто разбирался, почему приложение упирается в потолок под нагрузкой.
Как отвечать
Перечислите поставщиков потоков по слоям:
- HTTP-запросы: поток даёт встроенный контейнер, по умолчанию Tomcat со своим пулом, ограничение задаётся свойством
server.tomcat.threads.max. Один запрос держит поток целиком, пока не отдан ответ. На WebFlux картина другая: там небольшое число потоков событийного цикла; @Async: прокси передаёт вызов вTaskExecutor. Автоконфигурация Spring Boot поднимает бинapplicationTaskExecutorповерхThreadPoolTaskExecutor, настройки лежат в свойствахspring.task.execution;@Scheduled: отдельныйTaskScheduler, у которого пул по умолчанию состоит из одного потока;- свои пулы: обёрнутый в бин
ExecutorService, а также клиенты брокеров, драйверы и пул соединений с базой, которые заводят потоки самостоятельно.
Хорошо добавить, что это разные пулы и настраивать их надо согласованно. Двести потоков приложения при десяти соединениях к базе не ускорят ничего: задачи будут стоять в ожидании соединения.
Где спотыкаются
Считают, что @Async сам по себе распараллеливает метод. Аннотация работает через прокси, поэтому вызов такого метода изнутри того же класса идёт мимо прокси и выполняется синхронно.
Не смотрят на очередь пула. Очередь по умолчанию практически неограниченная, поэтому число потоков не растёт до максимума: задачи копятся в памяти, а не выполняются быстрее.
Забывают про единственный поток планировщика. Одна затянувшаяся задача сдвигает все остальные расписания, и со стороны это выглядит как «задачи перестали запускаться».