Как обрабатывать ситуацию переполнения очереди задач в ThreadPoolExecutor
Что проверяют
Знаете ли вы реальный порядок, в котором ThreadPoolExecutor принимает задачи, и понимаете ли, что политика отказа это решение о потере данных, а не настройка по вкусу. Следом часто спрашивают, почему Executors.newFixedThreadPool никогда не отклоняет задачи.
Как отвечать
Начните с алгоритма execute(). Пока потоков меньше corePoolSize, создаётся новый поток. Дальше задача идёт в workQueue. Только когда очередь полна, пул растёт до maximumPoolSize. Если и это исчерпано, вызывается RejectedExecutionHandler, как и для любой задачи после shutdown().
Отсюда вывод: переполнение возможно только с ограниченной очередью вроде ArrayBlockingQueue. У newFixedThreadPool очередь LinkedBlockingQueue без предела: она не переполняется, maximumPoolSize не работает, а при потоке задач быстрее обработки память растёт вплоть до OutOfMemoryError.
Встроенные политики:
AbortPolicy: по умолчанию, бросаетRejectedExecutionException, решение за вызывающим кодом;CallerRunsPolicy: задачу выполняет вызывающий поток. Это естественное противодавление, продюсер замедляется, пока занят чужой работой;DiscardPolicy: молча выбрасывает новую задачу;DiscardOldestPolicy: выбрасывает самую старую задачу из очереди и пробует снова.
Собственный обработчик обычно пишет лог и метрику, а дальше либо отдаёт задачу в запасное хранилище, либо возвращает клиенту ошибку вроде 503. Размер очереди и число активных потоков выводите в мониторинг, например через Micrometer.
В Spring у ThreadPoolTaskExecutor queueCapacity по умолчанию равен Integer.MAX_VALUE, это та же неограниченная очередь.
Где спотыкаются
Думают, что пул сначала растёт до maximumPoolSize, а потом копит очередь. Всё наоборот, поэтому пул с большой очередью живёт на corePoolSize потоках.
Ставят DiscardPolicy вместе с submit(). Выброшенная задача не выполнится, её Future никогда не завершится, и get() без таймаута зависнет навсегда.
Включают CallerRunsPolicy там, где вызывающий поток занимать нельзя: поток обработки HTTP-запросов или event loop. Сервер перестаёт принимать запросы, пока выполняет чужую задачу.