Что будет, если очередь пула потоков уже заполнена, но подаётся новая задача?
Если все потоки пула заняты и очередь задач заполнена и достигнут maximumPoolSize, вступает в действие политика отклонения задач (RejectedExecutionHandler).
Встроенные политики отклонения
| Политика | Поведение | Когда использовать |
|---|---|---|
AbortPolicy (по умолчанию) |
Выбрасывает RejectedExecutionException |
Когда потеря задачи недопустима и вызывающий должен об этом узнать |
CallerRunsPolicy |
Задача выполняется в потоке вызывающего (main, HTTP-thread и т.д.) |
Когда нужен «обратный нажим» (backpressure) – вызывающий поток замедляется |
DiscardPolicy |
Задача молча отбрасывается | Когда допустима потеря задач (телеметрия, логи) |
DiscardOldestPolicy |
Удаляется самая старая задача из очереди, новая ставится на её место | Когда свежие данные важнее старых |
Пример конфигурации
Пример: ThreadPoolExecutor с CallerRunsPolicy
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize
4, // maximumPoolSize
60, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(10), // Ограниченная очередь на 10 задач
new ThreadPoolExecutor.CallerRunsPolicy() // Политика отклонения
);
// Если все 4 потока заняты и очередь из 10 задач заполнена:
// 15-я задача выполнится в потоке вызывающего (main)
for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Задача " + taskId
+ " в потоке " + Thread.currentThread().getName());
try { Thread.sleep(1000); } catch (InterruptedException e) { }
});
}
executor.shutdown();
Собственная политика отклонения
Можно реализовать RejectedExecutionHandler:
Пример
RejectedExecutionHandler customHandler = (runnable, executor) -> {
logger.warn("Задача отклонена: очередь переполнена!");
// Записать в очередь повторных попыток, отправить в Dead Letter Queue и т.д.
};
Важные нюансы
- Неограниченная очередь (
LinkedBlockingQueueбез capacity) – никогда не заполнится, поэтомуRejectedExecutionHandlerне вызывается, но приложение может упасть сOutOfMemoryError. ПулыnewFixedThreadPool()иnewSingleThreadExecutor()используют неограниченную очередь – это потенциальная проблема. submit()vsexecute()– приAbortPolicyоба выбрасываютRejectedExecutionException, ноsubmit()сохраняет его вFuture.CallerRunsPolicyкак backpressure – самая полезная в production: вызывающий поток замедляется, что естественным образом ограничивает поступление новых задач.
Аналогия из жизни. Ресторан полностью занят и все столики зарезервированы. Пришёл новый гость.
AbortPolicy– «Извините, мест нет» (отказ).CallerRunsPolicy– «Официант, обслужи гостя сам, прямо у стойки» (вызывающий сам делает работу).DiscardPolicy– гость молча разворачивается и уходит.DiscardOldestPolicy– выгоняют того, кто дольше всех сидит.
На собеседовании. Назовите все 4 встроенные политики и опишите когда какую использовать. Подчеркните опасность
newFixedThreadPool()с неограниченной очередью – это частая причинаOutOfMemoryErrorв production. Рекомендуйте явно создаватьThreadPoolExecutorсArrayBlockingQueueиCallerRunsPolicyдля production-систем.