[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"question-mnogopotochnost-v-chyom-raznitsa-mezhdu-scopedvalue-i-threadlocal":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":21,"progress":22,"seo":23},298,"v-chyom-raznitsa-mezhdu-scopedvalue-i-threadlocal",8,"mnogopotochnost","Многопоточность","🔀","В чём разница между ScopedValue и ThreadLocal?","\u003C!-- grade: 5\u002F5 — подробное сравнение с примерами -->\n\n`ScopedValue` (JEP 464, preview в Java 21+) и `ThreadLocal` решают одну задачу — передача контекстных данных через стек вызовов без явной передачи параметров. Однако они фундаментально различаются в дизайне.\n\n**ThreadLocal — мутабельный контекст с ручным управлением:**\n\n```java\nprivate static final ThreadLocal\u003CString> USER_ID = new ThreadLocal\u003C>();\n\nvoid handleRequest(String userId) {\n    USER_ID.set(userId);\n    try {\n        processRequest(); \u002F\u002F может читать USER_ID.get() на любом уровне вложенности\n    } finally {\n        USER_ID.remove(); \u002F\u002F ОБЯЗАТЕЛЬНО очистить!\n    }\n}\n```\n\n**ScopedValue — иммутабельный контекст с автоматическим временем жизни:**\n\n```java\nprivate static final ScopedValue\u003CString> USER_ID = ScopedValue.newInstance();\n\nvoid handleRequest(String userId) {\n    ScopedValue.runWhere(USER_ID, userId, () -> {\n        processRequest(); \u002F\u002F может читать USER_ID.get()\n    });\n    \u002F\u002F После выхода из runWhere значение автоматически недоступно\n}\n```\n\n**Сравнительная таблица:**\n\n| Характеристика | `ThreadLocal` | `ScopedValue` |\n|---|---|---|\n| Изменяемость | Мутабельный (`set`\u002F`get`\u002F`remove`) | Иммутабельный (значение задаётся раз для scope) |\n| Время жизни | Пока жив поток или пока не вызван `remove()` | Ограничено блоком `runWhere`\u002F`callWhere` |\n| Наследование дочерними потоками | `InheritableThreadLocal` — копирование | Автоматически в `StructuredTaskScope.fork()` |\n| Производительность | Хэш-таблица в каждом потоке | Оптимизирован для чтения |\n| Утечки памяти | Частая проблема (забытый `remove()`) | Невозможны — автоматическая очистка |\n| Virtual Threads | Проблематичен при миллионах потоков | Создан для Virtual Threads |\n\n\u003Cdetails>\n\u003Csummary>Код: ScopedValue с StructuredTaskScope\u003C\u002Fsummary>\n\n```java\nprivate static final ScopedValue\u003CString> REQUEST_ID = ScopedValue.newInstance();\n\nString handleRequest(String requestId) throws Exception {\n    return ScopedValue.callWhere(REQUEST_ID, requestId, () -> {\n        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {\n            Subtask\u003CString> userData = scope.fork(() -> {\n                String id = REQUEST_ID.get(); \u002F\u002F доступно!\n                return fetchUser(id);\n            });\n            Subtask\u003CString> orderData = scope.fork(() -> {\n                String id = REQUEST_ID.get(); \u002F\u002F доступно!\n                return fetchOrders(id);\n            });\n            scope.join();\n            scope.throwIfFailed();\n            return userData.get() + \" : \" + orderData.get();\n        }\n    });\n}\n```\n\n\u003C\u002Fdetails>\n\n**Перебиндинг во вложенном scope:**\n\n`ScopedValue` можно «переопределить» во вложенном блоке — внутренний scope видит новое значение, внешний — старое:\n\n```java\nScopedValue.runWhere(USER_ID, \"admin\", () -> {\n    System.out.println(USER_ID.get()); \u002F\u002F \"admin\"\n    ScopedValue.runWhere(USER_ID, \"superadmin\", () -> {\n        System.out.println(USER_ID.get()); \u002F\u002F \"superadmin\"\n    });\n    System.out.println(USER_ID.get()); \u002F\u002F \"admin\" — восстановлено\n});\n```\n\n**Когда по-прежнему нужен ThreadLocal:**\n- Значение должно изменяться в процессе выполнения (накопление данных).\n- Код работает на Java \u003C 21.\n\n**Частые ошибки:**\n- `ThreadLocal` с Virtual Threads без `remove()` — утечка памяти при миллионах потоков.\n- `ScopedValue.get()` вне `runWhere`\u002F`callWhere` — бросит `NoSuchElementException`.\n- `InheritableThreadLocal` с пулом потоков — «чужое» значение при переиспользовании потока.\n\n> **Аналогия:** `ThreadLocal` — это доска с записями на рабочем столе каждого сотрудника: он может писать и стирать что угодно, и если забудет стереть — следующий за этим столом увидит чужие записи. `ScopedValue` — это бейджик, который надевается на время совещания и снимается автоматически при выходе.\n\n> **На собеседовании** ключевой вопрос: «Зачем нужен `ScopedValue`, если есть `ThreadLocal`?» Ответ: `ScopedValue` иммутабелен (нет race condition), не требует ручной очистки (нет утечек) и оптимизирован для Virtual Threads (нет хэш-таблицы на поток).","","senior",[15,16,17,18,19,20],"ScopedValue","Java-21","контекст","ThreadLocal","Virtual-Threads","concurrency",[],null,{"title":24,"description":25,"ogTitle":26,"ogDescription":27,"keywords":28,"schemaAnswer":34,"featuredSnippetReady":35},"ScopedValue vs ThreadLocal в Java 21 — ключевые различия — Gymterview","ScopedValue — иммутабельный, автоматически очищается, оптимизирован для Virtual Threads. ThreadLocal — мутабельный, требует remove(), проблемы с памятью.","ScopedValue vs ThreadLocal — что выбрать в Java 21?","ScopedValue иммутабелен и автоматически очищается. ThreadLocal мутабелен и требует ручного remove(). Для Virtual Threads рекомендуется ScopedValue.",[29,30,31,32,33],"ScopedValue Java","ScopedValue vs ThreadLocal","ThreadLocal проблемы","Virtual Threads контекст","JEP 464","ScopedValue (JEP 464, preview Java 21+) иммутабелен в пределах scope, автоматически очищается при выходе из runWhere\u002FcallWhere, оптимизирован для чтения без хэш-таблицы. ThreadLocal мутабелен (set\u002Fget\u002Fremove), живёт пока жив поток, часто вызывает утечки при забытом remove(). ScopedValue автоматически доступен в StructuredTaskScope.fork(). При миллионах Virtual Threads ThreadLocal создаёт огромное потребление памяти.",true]