Можно ли управлять пулом потоков в Spring Boot и какие есть конфигурации
Что проверяют
Понимаете ли вы, что за @Async и @Scheduled стоят два разных пула с разными умолчаниями, и умеете ли подбирать параметры под нагрузку. Следом спрашивают, что случится, когда задачи пойдут быстрее, чем пул их разбирает.
Как отвечать
Управлять можно и через настройки, и своим бином.
- пул для
@Async: автоконфигурируемыйThreadPoolTaskExecutor(бинapplicationTaskExecutor, он жеtaskExecutor),spring.task.execution.pool.core-sizeпо умолчанию 8,max-sizeиqueue-capacityне ограничены, префикс потоковtask-; - пул для
@Scheduled: отдельныйThreadPoolTaskScheduler,spring.task.scheduling.pool.sizeпо умолчанию 1, поэтому одна долгая задача задерживает все расписания; - свой бин:
@Configurationс@EnableAsyncплюсThreadPoolTaskExecutorBuilder, имя пула указывается в@Async("reportExecutor"); - виртуальные потоки:
spring.threads.virtual.enabled=trueна Java 21 и новее переводит задачи наSimpleAsyncTaskExecutorс виртуальными потоками, и подбор размера пула теряет смысл.
Обязательно проговорите порядок роста ThreadPoolExecutor: сначала потоки до core-size, затем очередь, и только на заполненной очереди пул растёт до max-size. С неограниченной очередью до max-size дело не доходит никогда, зато растёт heap. Боевая настройка это конечная queue-capacity плюс политика отказа: AbortPolicy бросает RejectedExecutionException, CallerRunsPolicy выполняет задачу в вызывающем потоке и создаёт обратное давление.
Закончите эксплуатацией: корректное завершение через spring.task.execution.shutdown.await-termination, метрики пула в Micrometer, перенос контекста (MDC, SecurityContext) через TaskDecorator.
Где спотыкаются
Ставят max-size в 200 и удивляются, что в пуле стабильно 8 потоков: очередь неограниченная, до максимума пул не дорастёт.
Вызывают @Async-метод из соседнего метода того же бина. Прокси обойдён, код выполняется синхронно, без единой ошибки в логе.
Возвращают void и теряют исключения: они уходят в AsyncUncaughtExceptionHandler, который по умолчанию только пишет в лог. Возвращайте CompletableFuture или задайте обработчик через AsyncConfigurer.