Получить доступ

В чём опасность ThreadLocal в пуле потоков?

MiddleМногопоточность
Коротко

Поток в пуле переиспользуется: значение ThreadLocal, не очищенное после задачи, протекает в следующую задачу (чужой пользователь, транзакция) и удерживает память. Правило: try { set } finally { remove() }.

  • Утечка сильнее в серверах приложений — держится загрузчик классов при передеплое
  • Фреймворки (Spring Security, MDC логов) очищают сами — свой код обязан тоже
  • Контекст не переходит в другие потоки: CompletableFuture и пулы его теряют

А ещё С виртуальными потоками — ScopedValue вместо ThreadLocal

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

ThreadLocal хранит значение для каждого потока отдельно — удобно для контекста запроса: текущий пользователь, id трассировки, соединение.

Проблема в пуле. Сервер обслуживает запросы пулом из, например, 200 потоков. Запрос A кладёт в ThreadLocal пользователя и не очищает. Поток возвращается в пул и берёт запрос B — тот видит пользователя A. Это не только ошибка, но и уязвимость.

Утечка памяти. Значение живёт, пока жив поток, а потоки пула живут вечно. Большие объекты в ThreadLocal накапливаются. В серверах приложений ещё хуже: значение ссылается на класс веб-приложения, и при передеплое старый загрузчик классов не может быть собран.

Правильно:

CONTEXT.set(user);
try {
    handle(request);
} finally {
    CONTEXT.remove();
}

Передача в другие потоки. Значение не переходит в задачу, отправленную в пул или в стадию CompletableFuture — контекст переносят явно (обёртка над Runnable, TaskDecorator в Spring).

Виртуальные потоки: их миллионы, и ThreadLocal на каждом дорог; рекомендуемая замена — ScopedValue (Java 25), значение, привязанное к области выполнения.

Что хотят услышать

  • Протекание контекста между задачами.
  • Утечка памяти из-за долгоживущих потоков.
  • remove в finally.
  • Потеря контекста при переходе в другой поток.

Частые ошибки

  • Забыть remove.
  • Ожидать, что значение видно в задаче из пула.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗ThreadLocal вообще не работает в пулах
  • ✗Значение очищается автоматически после задачи
  • ✗ThreadLocal замедляет поток
  • ✓Поток переиспользуется, поэтому значение «протекает» в следующую задачу и течёт память — нужен remove() в finally

Что спросят дальше

  • Как Spring хранит transaction context?
  • Почему MDC логгера чистят после запроса?

Разбор темы

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем