Почему ThreadLocal течёт в пуле потоков
Вопрос проверяет, понимаете ли вы жизненный цикл ThreadLocal и его связь с пулами потоков. В обычном коде поток создаётся и умирает, ThreadLocalMap умирает вместе с ним. В пуле поток живёт годами, и ThreadLocalMap живёт вместе с ним.
Каждая запись в ThreadLocalMap держит слабую ссылку на сам объект ThreadLocal, но сильную — на значение. Если после использования не вызвать remove(), значение остаётся привязанным к потоку до его завершения или до перезаписи тем же ключом.
private static final ThreadLocal FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public void process() { try { // работа с FORMATTER.get() } finally { FORMATTER.remove(); } }
В Tomcat и большинстве HTTP-серверов потоки из пула переживают тысячи запросов — забытый ThreadLocal накапливает объекты, которые никто не читает, но GC не может собрать.
Спросят, чем это отличается от утечки через static-поле — по механике идентично: сильная ссылка держит объект живым дольше, чем нужно. Разница в том, что ThreadLocal ловится реже, потому что каждый поток видит своё значение и баг не воспроизводится в однопоточном тесте.
remove() в finally — не защита от ошибок, а обязательная часть контракта ThreadLocal в пуле потоков.
Тренажёр: 600 вопросов, мок с таймером, план повторов
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки