TGViewer
Библиотека джависта | Java, Spring, Maven, Hibernate Библиотека джависта | Java, Spring, Maven, Hibernate @javaproglib · 22K subscribers
Post #7503 2.21K
🧵 Просто о сложном: ThreadLocal

В многопоточном приложении потоки разделяют общую память. Это создаёт проблемы с состоянием. ThreadLocal решает это элегантно — каждый поток получает свою копию переменной.

🔹 Как устроено внутри

Никакой центральной Map на уровне JVM нет. Всё проще: у каждого объекта Thread есть поле threadLocals типа ThreadLocal.ThreadLocalMap. Это внутренняя Map, которая живёт внутри самого потока.

Когда ты вызываешь set() — значение кладётся в Map текущего потока, где ключ — сам объект ThreadLocal:
// упрощённо, внутри ThreadLocal.set(value)
Thread.currentThread().threadLocals.put(this, value);

get() — достаёт из Map того же потока. Другой поток обращается к своей Map и видит своё значение. Пересечений нет по конструкции.

🔹 Инициализация через withInitial

Если get() вызван, а set() ещё не было, возвращается null. Чтобы этого избежать, используют фабрику:
ThreadLocal<SimpleDateFormat> sdf =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

Лямбда вызовется один раз на поток, при первом get(). Дальше кешируется в той же threadLocals Map.

🔹 Что реально использует ThreadLocal в экосистеме

Spring держит через него всё, что должно быть доступно в рамках одного запроса без явной передачи через параметры:

→ TransactionSynchronizationManager — текущее соединение с БД, чтобы сервис и репозиторий в рамках @Transactional работали с одним коннектом
→ SecurityContextHolder — аутентифицированный пользователь
→ RequestContextHolder — текущий HttpServletRequest

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

🔹 Где ломается

Thread Pool. Потоки переиспользуются и после обработки запроса поток возвращается в пул, но его threadLocals Map никуда не девается. Если не вызвать remove(), то следующий запрос, попавший в этот поток, найдёт чужие данные.

Это не гипотетическая проблема. Именно так утекают security-контексты и появляются баги, которые воспроизводятся только под нагрузкой.

Второй момент — утечка памяти. ThreadLocalMap использует WeakReference на ключ, но значение держится сильной ссылкой. Если поток живёт долго (а в пуле — фактически вечно) и remove() никто не вызвал — объект не будет собран GC, пока поток жив.

Поэтому паттерн всегда один:
try {
tl.set(value);
// работа
} finally {
tl.remove();
}


ThreadLocal решает конкретную задачу — изоляция состояния без синхронизации. Не серебряная пуля, не замена synchronized. Просто инструмент для случаев, когда данные должны жить в рамках потока, а не шариться между ними.

══════ Навигация ══════
Вакансии • Задачи • Собесы

🐸 Библиотека джависта

#CoreJava
  • 👍 7
  • ❤ 5
  • 🔥 2
More from @javaproglib
  1. Oct 17, 2026☕️ Скользящие окна в Stream без ручного буфера Нужно сравнить соседние измерения или обраб…
  2. Oct 16, 2026🧑‍💻 Какие навыки помогают вам в разработке в 2026 году? Что вы чаще используете в работе…
  3. Oct 16, 2026🧩 Неизменяемый список всё ещё может измениться Collections.unmodifiableList() запрещает м…
  4. Oct 15, 2026💡 Logpoints: логи без правки кода и нового деплоя Чтобы увидеть значение переменной, необ…
  5. Oct 14, 2026Post #7897
  6. Oct 14, 2026Почему разработчик планирует отдавать больше задач Claude Винсент Шмальбах описал план пер…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →