
在 Java 并发编程中,线程本地变量(ThreadLocal) 是一个被广泛使用的工具,用于在线程内部存储和传递数据,确保每个线程都有自己的变量副本。但是ThreadLocal使用中越来越多的问题被发现,如ThreadLocal值的可变性,需要额外的同步措施来保证线程安全;需要显式调用
remove(),否则可能导致内存泄漏和数据污染(在线程池中尤为严重)。而随着现代 Java 引入的虚拟线程(Project Loom)以及对更安全、更易于调试的并发模型的需求,一种新的机制应运而生——
ScopeValue。

ScopeValue 是 Java 21 (JEP 446) 引入的一个孵化中的 API(孵化状态在后续版本中可能移除),旨在提供一种更优越、更结构化的方式来共享不可变数据。它的主要特性包括:
ScopeValue 所持有的值在绑定后是不可变的。这意味着一旦通过 ScopeValue.where() 方法将一个值绑定到当前范围(Scope),该值就不能被更改。
这样可以确保数据在共享和传递过程中的一致性,消除了并发修改的风险,极大地简化了代码的推理和调试。
ScopeValue 的值只在其动态范围 内可见和有效。这个范围由 ScopeValue.where(value).run(() -> ...) 或 ScopeValue.where(value).call(() -> ...) 方法所创建。
这样实现的好处是可以保证值只在绑定的代码块及其直接或间接调用的方法中可见。一旦代码块执行完毕,该绑定就会自动撤销。这使得数据的生命周期与代码的执行结构紧密耦合,符合结构化并发的理念。
ScopeValue 的值可以透明地从父线程/调用者传递给子线程或被调用的任务,特别是对于 虚拟线程 (Virtual Threads)。这解决了 ThreadLocal 在虚拟线程或异步任务中传递上下文时的痛点,不再需要手动传递或使用复杂的继承机制。
package org.example;
import java.lang.ScopedValue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ScopeValueDemo {
private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
public static void processRequest(String id) {
System.out.println("--- 线程: " + Thread.currentThread().getName() + " 开始处理 ---");
ScopedValue.where(REQUEST_ID, id).run(() -> {
try {
System.out.println("步骤 A:设置 ID -> " + REQUEST_ID.get());
System.out.println("步骤 A:内部逻辑获取 ID -> " + REQUEST_ID.get());
} catch (Exception e) {
// do nothing
}
});
try {
System.out.println("步骤 C:获取 ID (范围外) -> " + REQUEST_ID.get());
} catch (java.util.NoSuchElementException e) {
System.out.println("步骤 C:ID 已自动清除 (ScopeValue 优势)");
}
System.out.println("--- 线程: " + Thread.currentThread().getName() + " 结束处理 ---\n");
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> processRequest("SCOPE-1"));
Thread.sleep(100);
executor.submit(() -> processRequest("SCOPE-2"));
executor.shutdown();
}
}执行结果:

示例代码中,直接使用:
ScopedValue.where(REQUEST_ID, id).run(() -> {
try {
System.out.println("步骤 A:设置 ID -> " + REQUEST_ID.get());
System.out.println("步骤 A:内部逻辑获取 ID -> " + REQUEST_ID.get());
} catch (Exception e) {
// do nothing
}
});就将变量绑定到方法内部逻辑中,而超出该作用域范围的,资源会自动清理。这就不需要像ThreadLocal一样需要手动去释放。
简单总结一下两者的对比:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。