作为Java 21的重要预览功能,ScopedValue通过结构化并发机制实现了线程间数据的安全传递,解决了传统参数传递方式的局限性。

ScopedValue作为一种线程安全的不可变容器,支持在动态作用域内共享数据,避免了显式方法参数传递的繁琐。
核心特性:
传统方式的问题:
// 方式1:层层传递参数(代码冗长)
void handleRequest(HttpRequest req) {
String requestId = req.getId();
processStep1(requestId); // 每层都需要传
processStep2(requestId);
processStep3(requestId);
}// 方式2:ThreadLocal(生命周期难管理、易泄漏)
private static final ThreadLocal CONTEXT = new ThreadLocal<>();
void handleRequest(HttpRequest req) {
CONTEXT.set(req.getId());
try {
processStep1();
processStep2();
} finally {
CONTEXT.remove(); // 必须手动清理!
}
}
ScopedValue的优势:
// 定义 ScopedValue(静态 final)
private static final ScopedValue REQUEST_ID =
ScopedValue.newInstance();// 绑定值并执行
ScopedValue.runWhere(REQUEST_ID, "req-123", () -> {
processStep1(); // 可直接读取 REQUEST_ID
processStep2();
processStep3();
}); // 自动清除绑定
// 不可变值
ScopedValue<String> NAME = ScopedValue.newInstance();// 可封装可变状态(推荐用 record)
record UserContext(String userId, String traceId) {}
ScopedValue CONTEXT = ScopedValue.newInstance();
// 方式1:runWhere (Runnable)
ScopedValue.runWhere(SCOPED_VALUE, "hello", () -> {
System.out.println(SCOPED_VALUE.get()); // "hello"
});// 方式2:callWhere (Callable,有返回值)
String result = ScopedValue.callWhere(SCOPED_VALUE, "data",
() -> someService.process()
);// 方式3:where (接受 Runnable/Callable/Function)
Consumer<String> task = ScopedValue.where(SCOPED_VALUE, "val",
() -> doWork()
);
// 在绑定作用域内读取
String val = SCOPED_VALUE.get(); // 返回绑定的值// 获取 Optional(未绑定时返回空)
Optional opt = SCOPED_VALUE.orElse(null);// 检查是否绑定
boolean isBound = SCOPED_VALUE.isBound();
// 映射到另一个 ScopedValue
ScopedValue LENGTH = NAME.map(String::length);
ScopedValue.runWhere(NAME, "hello", () -> {
System.out.println(LENGTH.get()); // 5
});// flatMap(用于嵌套 Optional)
ScopedValue> OPT = NAME.map(v -> Optional.of(v));
ScopedValue FLAT = OPT.flatMap(Optional::stream);
ScopedValue<String> KV = ScopedValue.newInstance();void outer() {
ScopedValue.runWhere(KV, "outer-value", () -> {
inner(); // 子方法可访问 KV
});
// 此处 KV 已恢复为未绑定状态
}void inner() {
String v = KV.get(); // "outer-value"
}
关键规则:
ScopedValue.runWhere(KV, "outer", () -> {
System.out.println(KV.get()); // "outer"
ScopedValue.runWhere(KV, "inner", () -> {
System.out.println(KV.get()); // "inner"
});
System.out.println(KV.get()); // "outer"(恢复)
});
| 维度 | ThreadLocal | ScopedValue |
|---|---|---|
| 生命周期 | 手动管理 | 自动绑定/清除 |
| 继承性 | 子线程可继承 | 不继承 |
| 内存泄漏风险 | 高 | 无 |
| 适用场景 | 传统线程池 | 虚拟线程 |
| 性能 | 相对较低 | 高 |
ScopedValue专为结构化并发机制设计。
// 使用 StructuredTaskScope 并发执行多个任务
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 绑定上下文到整个作用域
ScopedValue.runWhere(CONTEXT, userContext, () -> {
// 所有子任务自动继承该上下文
scope.fork(() -> taskA()); // taskA 可读取 CONTEXT
scope.fork(() -> taskB()); // taskB 可读取 CONTEXT
scope.join(); // 等待所有任务完成
});
}
优势:
ScopedValue与虚拟线程完美配合:
ScopedValue REQUEST_ID = ScopedValue.newInstance();void handleRequest(HttpRequest req) {
// 为每个虚拟线程绑定独立的请求 ID
ScopedValue.runWhere(REQUEST_ID, req.getId(), () -> {
Thread.startVirtualThread(() -> {
log(); // 可读取 REQUEST_ID
process(); // 可读取 REQUEST_ID
});
});
}
注意:
ScopedValue TRACE_ID = ScopedValue.newInstance();
ScopedValue SPAN_ID = ScopedValue.newInstance();void handleHttpRequest(HttpRequest req) {
String traceId = req.header("X-Trace-Id");
String spanId = req.header("X-Span-Id"); ScopedValue.runWhere(TRACE_ID, traceId, () ->
ScopedValue.runWhere(SPAN_ID, spanId, () -> {
processBusinessLogic();
callDatabase();
callExternalApi();
})
);
}void log() {
String traceId = TRACE_ID.get();
String spanId = SPAN_ID.get();
logger.info("处理完成 traceId={} spanId={}", traceId, spanId);
}
ScopedValue CURRENT_USER = ScopedValue.newInstance();void authenticateAndProcess(HttpRequest req) {
User user = authenticate(req);
ScopedValue.runWhere(CURRENT_USER, user, () -> {
authorize();
executeBusiness();
});
}void authorize() {
User user = CURRENT_USER.get();
checkPermission(user.getRole());
}
record RequestContext(
String requestId,
String locale,
TimeZone timeZone,
Map metadata
) {}ScopedValue REQUEST_CTX = ScopedValue.newInstance();void processRequest(HttpRequest req) {
RequestContext ctx = buildContext(req);
ScopedValue.runWhere(REQUEST_CTX, ctx, () -> {
validate();
transform();
respond();
});
}void respond() {
RequestContext ctx = REQUEST_CTX.get();
resp.header("X-Request-Id", ctx.requestId());
}
继承线程的隐式传递:
快速访问:
// 伪代码:内部实现
public T get() {
return Thread.currentThread()
.scopedValueBindings()
.get(this);
}
不可变性保证:
根据JDK基准测试:
| 操作 | ThreadLocal | ScopedValue | 性能提升 |
|---|---|---|---|
| 设置值 | 100 ns | 15 ns | 6.7x |
| 获取值 | 80 ns | 10 ns | 8x |
| 清理 | 90 ns | 自动 | N/A |
性能优势原因:
ScopedValue在JDK 21成为标准API。
ScopedValue V = ScopedValue.newInstance();
ScopedValue.runWhere(V, "parent", () -> {
Thread.startVirtualThread(() -> {
System.out.println(V.get()); // 异常!
});
});
每个实例只能绑定一个值。
不适合跨线程阻塞等待场景。
不适用于传统线程池等场景。
| 技术 | 主要用途 | 生命周期管理 | 适用场景 |
|---|---|---|---|
| ThreadLocal | 线程私有数据 | 手动 | 传统多线程 |
| ScopedValue | 结构化数据 | 自动 | 虚拟线程 |
| Context Propagation | 跨线程传播 | 框架管理 | 特定框架 |
核心考点:
常见面试题:
Q:与ThreadLocal区别?
A:生命周期自动管理,性能更高,专为虚拟线程设计。
Q:为什么不会自动继承?
A:需在绑定作用域内创建虚拟线程。
Q:支持多值传递吗?
A:通过多个实例或复合对象实现。
Q:线程安全吗?
A:值不可变,天然线程安全。
# 官方示例
https://github.com/openjdk/jdk/tree/jdk-21+35/src/java.base/share/classes/java/lang
ScopedValue作为Java 21的重要特性,通过结构化作用域机制为现代并发编程提供了更安全高效的解决方案。