Что такое ThreadLocal в Java и зачем он нужен
Что проверяют
Понимает ли кандидат, где это законный инструмент, а где замаскированная глобальная переменная. Главное, что хотят услышать: как значения ведут себя в пуле потоков и почему без явной очистки это утечка памяти и чужие данные в соседнем запросе.
Как отвечать
ThreadLocal это переменная, у которой своя копия у каждого потока. Значение хранится не в объекте ThreadLocal, а в карте внутри самого потока, где ключом служит ссылка на экземпляр переменной. Отсюда свойство: синхронизация не нужна, потоки не видят значений друг друга.
private static final ThreadLocal<Ctx> CTX = ThreadLocal.withInitial(Ctx::new);
try {
CTX.set(ctx);
handle(request);
} finally {
CTX.remove();
}Типичные применения: контекст запроса (пользователь, трассировка, локаль), привязка транзакции и соединения к потоку, экземпляры небезопасных для многопоточной работы форматтеров. Так устроены контекст безопасности и управление транзакциями в популярных фреймворках.
Обязательная часть ответа это remove() в finally: в пуле поток переиспользуется, и невычищенное значение достанется следующей задаче. Вдобавок ключ в карте потока хранится слабой ссылкой, а значение сильной, поэтому пока живёт поток, живёт и объект.
Ограничения: дочерний поток значения не наследует без специального варианта класса, а с пулами наследование смысла не имеет; на виртуальных потоках копий становится слишком много, поэтому в свежих версиях JDK появилась альтернатива с неизменяемыми значениями и явной областью видимости.
Где спотыкаются
Забывают очистку. В обработчике запросов следующий запрос на том же потоке видит чужого пользователя, а в контейнере приложений это ещё и не даёт выгрузить классы.
Пытаются лечить этим гонку за общим изменяемым объектом. Если в каждый поток положить ссылку на один и тот же объект, изоляции не будет: раздельны переменные, а не данные, на которые они указывают.
Протягивают его через все слои как удобный глобал. Зависимость становится невидимой в сигнатурах, тесты начинают падать при параллельном запуске, а любой переход в другой поток или асинхронную задачу теряет контекст, и его приходится копировать руками.