Почему лучше использовать thread pool в Java, а не создавать потоки вручную
Что проверяют
Понимаете ли вы, что пул нужен не столько ради экономии на создании потоков, сколько ради ограничения конкурентности. Ответ «создавать поток дорого, пул их переиспользует» верен, но короток: следом спросят, какого размера пул вы возьмёте и что произойдёт, когда очередь переполнится.
Как отвечать
Поток платформы это поток операционной системы: под него резервируется стек, создание идёт через ядро. Схема «поток на задачу» под нагрузкой упирается в память и падает с ошибкой создания нативного потока, причём деградация наступает резко.
Пул решает три задачи:
- ограничивает число одновременно работающих задач, то есть даёт обратное давление через очередь и политику отказа;
- амортизирует создание потоков;
- даёт управление: именование потоков для читаемых дампов, завершение через
shutdown, метрики очереди, обработку ошибок.
Дальше говорите конкретикой. submit кладёт исключение задачи в Future, и без вызова get оно теряется молча. Фабрики из Executors удобны, но newFixedThreadPool берёт неограниченную очередь, а newCachedThreadPool неограниченно плодит потоки, поэтому в бою обычно собирают ThreadPoolExecutor руками, с конечной очередью и понятной политикой отказа.
Размер подбирают по характеру работы: для расчётов около числа ядер, для блокирующего ввода и вывода больше, пропорционально доле ожидания. Разные виды работы разводят по разным пулам. Уместно добавить, что виртуальные потоки меняют картину для блокирующего кода, но ограничивать доступ к внешнему ресурсу всё равно приходится семафором.
Где спотыкаются
Считают неограниченную очередь защитой. Потоки не кончатся, кончится память: приложение умирает не от числа потоков, а от объёма накопленных в очереди задач.
Второе: забывают про завершение. Обычные потоки пула не дают виртуальной машине выйти, а shutdownNow лишь выставляет флаг прерывания, и задача, которая его не проверяет, продолжает работать.
Третье: складывают всё в один пул и ждут там результат задачи, отправленной в тот же пул. Медленная зависимость выедает потоки, а взаимное ожидание внутри одного пула даёт классическое голодание, когда всё встаёт без единого исключения в логах.