[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"question-mnogopotochnost-chto-takoe-threadlocal-peremennaya":3},{"id":4,"slug":5,"topicId":6,"topicSlug":7,"topicName":8,"topicEmoji":9,"question":10,"answer":11,"codeLang":12,"codeSrc":12,"important":12,"commonMistakes":12,"modernUsage":12,"difficulty":13,"tags":14,"related":19,"progress":20,"seo":21},274,"chto-takoe-threadlocal-peremennaya",8,"mnogopotochnost","Многопоточность","🔀","Что такое ThreadLocal-переменная?","\u003C!-- grade: 4\u002F5 — хорошее объяснение, добавлена секция о Virtual Threads; расширены примеры и аналогия -->\n\n`ThreadLocal` — это класс из пакета `java.lang`, который позволяет создать переменную, имеющую **отдельное, независимое значение для каждого потока**. Иными словами, один и тот же объект `ThreadLocal` хранит разные данные в зависимости от того, из какого потока к нему обращаются.\n\n**Как устроено внутри.** У каждого экземпляра `Thread` существует скрытое поле `threadLocals` — это хэш-таблица типа `ThreadLocal.ThreadLocalMap`. Ключом служит ссылка на объект `ThreadLocal`, значением — объект, «положенный» в эту переменную вызовом `set()`. Когда вы вызываете `get()`, класс `ThreadLocal` заглядывает в таблицу текущего потока (`Thread.currentThread()`) и возвращает значение, ассоциированное с данным ключом.\n\n```java\n\u002F\u002F Объявляем ThreadLocal-переменную\nThreadLocal\u003CString> userContext = new ThreadLocal\u003C>();\n\n\u002F\u002F В потоке A\nuserContext.set(\"admin\");\nSystem.out.println(userContext.get()); \u002F\u002F \"admin\"\n\n\u002F\u002F В потоке B (одновременно)\nuserContext.set(\"guest\");\nSystem.out.println(userContext.get()); \u002F\u002F \"guest\"\n\u002F\u002F Поток A по-прежнему видит \"admin\"\n```\n\n**Инициализация через `withInitial`.** Начиная с Java 8, удобнее задавать значение по умолчанию фабричным методом:\n\n```java\nThreadLocal\u003CSimpleDateFormat> dateFormat =\n    ThreadLocal.withInitial(() -> new SimpleDateFormat(\"yyyy-MM-dd\"));\n```\n\nТеперь каждый поток при первом вызове `get()` автоматически получит собственный экземпляр `SimpleDateFormat`, что решает проблему потоконебезопасности этого класса.\n\n**Типичные сценарии использования:**\n\n| Сценарий | Пример |\n|---|---|\n| Хранение контекста запроса | Идентификатор пользователя, request ID для логирования |\n| Потоконебезопасные объекты | `SimpleDateFormat`, `NumberFormat`, `Random` |\n| Соединения с БД | Одно соединение на поток в пуле |\n| Транзакционный контекст | Spring `@Transactional` хранит контекст в `ThreadLocal` |\n\n**Важно: изоляция касается ссылок, а не самих объектов.** Если два потока через свои `ThreadLocal`-слоты ссылаются на один и тот же мутабельный объект, конкурентные модификации возможны. `ThreadLocal` изолирует именно ссылки.\n\n**Инициализация в правильном потоке.** Распространённая ошибка — вызвать `set()` в главном потоке, а затем ожидать, что значение будет доступно в рабочем потоке. Этого не произойдёт: `set()` привязывает значение к потоку, в котором вызван, поэтому рабочий поток при вызове `get()` получит `null` (или значение `withInitial`).\n\n**Обязательная очистка.** После окончания работы необходимо вызывать `remove()`, особенно при использовании пулов потоков. Без этого значение «перетекает» в следующую задачу, исполняемую тем же потоком из пула:\n\n```java\nThreadLocal\u003CString> requestId = new ThreadLocal\u003C>();\n\nvoid handleRequest(String id) {\n    requestId.set(id);\n    try {\n        processRequest();\n    } finally {\n        requestId.remove(); \u002F\u002F обязательно!\n    }\n}\n```\n\n**Проблемы ThreadLocal с Virtual Threads (Java 21+):**\n\nС появлением виртуальных потоков `ThreadLocal` создаёт серьёзные проблемы:\n\n1. **Потребление памяти** — при миллионах виртуальных потоков каждый `ThreadLocal` создаёт запись в хэш-таблице потока, что может привести к `OutOfMemoryError`.\n2. **Утечки памяти** — если забыть вызвать `remove()`, значение хранится до завершения потока. В пуле Platform Threads поток переиспользуется и значение «перетекает» в следующую задачу. С Virtual Threads (которые одноразовые) утечка другого рода — огромное количество живых записей.\n3. **`InheritableThreadLocal`** — при использовании с пулом потоков значение копируется при создании потока, но потоки переиспользуются, и следующая задача видит «чужое» значение.\n\n**Рекомендация:** для новых проектов на Java 21+ вместо `ThreadLocal` используйте `ScopedValue` (см. соответствующий вопрос) — он иммутабелен, автоматически очищается и оптимизирован для Virtual Threads.\n\n> **Аналогия:** представьте офис с общими шкафчиками. У каждого сотрудника (потока) есть свой ящик с одним и тем же номером (один `ThreadLocal`), но содержимое внутри — индивидуальное. Уборщица не знает, кому принадлежит ящик, поэтому если сотрудник уволился (поток завершился), а ящик не очищен — получается утечка.\n\n> **На собеседовании** могут спросить: «Где в реальных фреймворках используется `ThreadLocal`?» Хороший ответ: Spring хранит транзакционный контекст (`TransactionSynchronizationManager`), Security-контекст (`SecurityContextHolder`) и RequestAttributes через `ThreadLocal`. Именно поэтому при переходе на Virtual Threads и Structured Concurrency эти компоненты обновляются для поддержки `ScopedValue`.","","middle",[15,16,17,18],"потоки","ThreadLocal","изоляция-данных","concurrency",[],null,{"title":22,"description":23,"ogTitle":24,"ogDescription":25,"keywords":26,"schemaAnswer":32,"featuredSnippetReady":33},"Что такое ThreadLocal-переменная в Java — изоляция данных потоков — Gymterview","ThreadLocal позволяет каждому потоку иметь собственную копию переменной. Разбор механизма, проблем с Virtual Threads и рекомендация использовать ScopedValue.","ThreadLocal-переменная в Java — зачем нужна и как работает","ThreadLocal хранит отдельную копию переменной для каждого потока. С Virtual Threads (Java 21+) рекомендуется ScopedValue.",[27,28,29,30,31],"ThreadLocal Java","ThreadLocal переменная","изоляция данных потоков","ThreadLocal Virtual Threads","ScopedValue","ThreadLocal — класс, позволяющий имея одну переменную, иметь различное её значение для каждого из потоков. У каждого потока есть ассоциированная таблица ThreadLocal-переменных. Доступ к значению осуществляется через методы get() и set(). ThreadLocal изолирует ссылки, а не сами объекты. С появлением Virtual Threads (Java 21+) ThreadLocal создаёт проблемы с потреблением памяти — рекомендуется использовать ScopedValue.",true]