Почему ExecutorService лучше создавать потоки, чем создавать их напрямую
Что проверяют
Понимаете ли вы, что поток это ресурс операционной системы, и умеете ли отделять задачу от того, кто её исполняет. Заодно смотрят, знаете ли вы оборотную сторону пулов, потому что про плюсы рассказывают все.
Как отвечать
new Thread на каждую задачу означает платформенный поток под каждую задачу: обращение к ядру, отдельный стек, планирование операционной системой. Число потоков при этом ничем не ограничено, и всплеск нагрузки превращается в тысячи потоков, которые больше переключаются, чем работают. Пул решает это разделением: есть задача (Runnable, Callable) и есть исполнитель.
- потоки переиспользуются, их количество задано и предсказуемо;
- очередь сглаживает всплеск вместо бесконтрольного роста;
Futureвозвращает результат и позволяет отменить задачу;shutdownиawaitTerminationдают внятное завершение,ScheduledExecutorServiceзакрывает периодические задачи.
Сразу добавьте оговорку про фабрики Executors: newFixedThreadPool ставит перед пулом неограниченную очередь, поэтому при перегрузке сервис не отказывает, а копит задачи до нехватки памяти, а newCachedThreadPool наоборот плодит потоки без предела. Взрослый вариант это ThreadPoolExecutor, собранный руками: ограниченная очередь, именованная фабрика потоков, чтобы имена читались в дампе, и явная политика отказа.
Где спотыкаются
Называют плюсом удобную обработку ошибок. На деле submit исключение проглатывает: оно лежит внутри Future и всплывает только при вызове get. Периодическая задача при этом просто перестаёт выполняться, а в логе пусто. У execute исключение хотя бы доходит до обработчика необработанных исключений потока.
Второе: забывают завершить пул. Потоки пула не демоны, поэтому приложение не выходит, пока пул жив. И shutdown не прерывает уже выполняющиеся задачи, для этого есть shutdownNow, а задача должна корректно реагировать на прерывание.
Третье: отправляют в один пул всё подряд и получают взаимную блокировку. Задача, которая ждёт результата другой задачи того же небольшого пула, держит поток и не даёт второй задаче начаться. Разделяйте пулы по типу работы и помните, что пул не отменяет синхронизацию общего состояния.