Можно ли переиспользовать потоки
Что проверяют
Отличаете ли вы объект Thread от потока операционной системы. Вопрос короткий, но ответ сразу показывает, работали ли вы с пулами: у объекта и у ресурса под ним разные правила переиспользования.
Как отвечать
Разделите две вещи. Экземпляр Thread переиспользовать нельзя: после завершения run поток переходит в состояние TERMINATED, и повторный start даёт IllegalThreadStateException. А сам поток как ресурс переиспользуется постоянно, и на этом стоит весь пул: рабочий поток забирает задачу из очереди, выполняет её, возвращается за следующей. Создание потока стоит стека в памяти и обращения к операционной системе, поэтому пул почти всегда выгоднее, чем поток на задачу.
Отсюда практический ответ: не создавайте потоки руками, берите ExecutorService, а когда нужен контроль над очередью и политикой отказа, настраивайте ThreadPoolExecutor напрямую. Для задач, которые в основном ждут ввода и вывода, в свежих версиях языка есть виртуальные потоки, и вот их пулить как раз не надо: они дёшевы, модель там «поток на задачу», а пул только мешает.
Чтобы показать глубину, скажите про чистоту состояния. Переиспользуемый поток приносит следующей задаче всё, что оставила предыдущая: значения ThreadLocal, изменённое имя, выставленный флаг прерывания, контекст логирования.
Где спотыкаются
Говорят, что переиспользование опасно из-за гонок. Гонки возникают из-за общего изменяемого состояния, а не оттого, что задача выполнилась на уже поработавшем потоке.
Забывают чистить ThreadLocal. В пуле поток живёт неделями, и значение, положенное одним запросом, прочитает другой. Отсюда чужие данные в логах и утечки памяти, поэтому после работы вызывают remove.
Теряют исключения. Задача, отправленная через submit, при падении не печатает ничего: ошибка складывается в Future и молчит, пока не позовут get. Пул при этом выглядит здоровым, а задачи тихо не выполняются.