Что проверяет вопрос «что будет, когда очередь ThreadPoolExecutor заполнена»

Вопрос проверяет, знаете ли вы порядок, в котором пул решает судьбу новой задачи. Большинство отвечает «создастся новый поток», и это неверно в самом частом случае.

Порядок такой: если потоков меньше corePoolSize — создать поток. Иначе положить задачу в очередь. И только если очередь не приняла — создать поток сверх core, до maximumPoolSize. Если и потоков уже maximum — отдать задачу RejectedExecutionHandler.

Отсюда ловушка: Executors.newFixedThreadPool() создаёт LinkedBlockingQueue без лимита. Такая очередь принимает всегда, поэтому потоков сверх core не будет никогда, отказа не будет никогда, а память под задачи будет расти до OutOfMemoryError. Пул «не тормозит», он копит.

var pool = new ThreadPoolExecutor( 4, 16, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1_000), new ThreadPoolExecutor .CallerRunsPolicy());

Четыре стандартных обработчика отказа: AbortPolicy бросает RejectedExecutionException (это умолчание), DiscardPolicy молча выбрасывает задачу, DiscardOldestPolicy выбрасывает самую старую из очереди, CallerRunsPolicy выполняет задачу в потоке вызывающего.

Дальше обычно спрашивают, зачем CallerRunsPolicy. Это простейший backpressure: пока поток-отправитель занят чужой задачей, он не генерирует новые, и очередь успевает разгрузиться. Цена — задержка в вызывающем потоке; для HTTP-обработчика это значит, что запрос будет висеть, пока не выполнится чужая работа. Ещё одна ловушка: после shutdown() любая новая задача тоже уходит в обработчик отказа, и с умолчанием AbortPolicy это исключение в момент, когда вы его не ждёте.

Очередь стоит перед потоками сверх core, а не после. Безлимитная очередь превращает maximumPoolSize в декорацию. Обработчик отказа — не аварийная кнопка, а часть дизайна: он определяет, кто платит за перегрузку.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки