ThreadLocal
ThreadLocal<T> дает каждому потоку отдельное значение для одного ключа:
private static final ThreadLocal<RequestContext> CONTEXT =
ThreadLocal.withInitial(RequestContext::new);
void handle(Request request) {
try {
CONTEXT.set(contextFrom(request));
process(request);
} finally {
CONTEXT.remove();
}
}Значение хранится не «внутри ThreadLocal», а в структуре, связанной с текущим Thread. Ключи там удерживаются слабыми ссылками, значения — нет. Если забыть remove() в долгоживущем thread pool, следующий запрос может увидеть старый контекст, а объект способен оставаться в памяти до очистки stale entry или завершения потока.
Подходящие случаи
- request/correlation context в синхронной цепочке;
- не-thread-safe объект, который оправданно переиспользуется одним потоком;
- технический контекст, который невозможно разумно передавать параметром.
Явный параметр обычно прозрачнее: зависимости видны в сигнатуре и не привязаны к модели потоков.
Границы
- значение не передается автоматически в задачи executor;
- один запрос может продолжиться на другом потоке;
- один поток пула обслуживает много запросов;
InheritableThreadLocalкопирует значение при создании дочернего потока, поэтому с заранее созданными пулами не решает propagation.
В реактивной цепочке или при асинхронных стадиях нужен контекст соответствующего фреймворка либо явная передача.
На собеседовании
- Почему
remove()обязателен в пуле? Поток живет дольше запроса, иначе утечка данных и памяти. - Делает ли ThreadLocal общий объект thread-safe? Нет; он должен хранить отдельный объект на поток.
- Переносится ли значение в executor? Обычно нет.
- Где физически хранится значение? В ThreadLocalMap текущего
Thread. - Почему слабого ключа недостаточно против утечки? Значение остается сильно достижимым через map до очистки записи.