Scoped Values(JEP 506)#
版本信息
- 最低 JDK:25(21–24 预览 → 25 正式)
- JEP:JEP 506: Scoped Values
为什么需要#
JDK 8 里"线程局部数据"用 ThreadLocal:可变、生命周期无界、容易内存泄漏,且与虚拟线程不太合拍(虚拟线程海量,ThreadLocal 的可变共享会破坏它们的不可变性假设)。ScopedValue(JEP 506,转正)提供不可变、有界的作用域局部值——值只在一段代码作用域内有效,结束自动消失,天然适合虚拟线程与结构化并发。
语法#
examples/scoped-values/ 真实源码(snippets 嵌入零漂移):
package com.javamodern.scoped;
import java.util.function.Supplier;
/**
* 演示 JDK 25 Scoped Values(JEP 506,转正)。
*
* <p>{@code ScopedValue}(在 {@code java.lang},无需 import)是不可变、有界的"作用域局部"值,
* 适合与虚拟线程配合,替代 {@code ThreadLocal} 的可变、无界生命周期带来的内存泄漏与线程安全风险。
*/
public class ScopedValueDemo {
/** 当前用户的作用域值。 */
public static final ScopedValue<String> USER = ScopedValue.newInstance();
/** 在以 {@code user} 绑定的作用域内执行 {@code body},返回其结果。 */
public static <T> T runAs(String user, Supplier<T> body) {
return ScopedValue.where(USER, user).call(body::get); // (1)
}
/** 当前是否处于 USER 绑定的作用域内(作用域外为 false)。 */
public static boolean isUserBound() {
return USER.isBound();
}
public static void main(String[] args) {
String who = runAs("alice", () -> "running as " + USER.get());
System.out.println(who);
System.out.println("bound outside = " + isUserBound());
}
}
- :material-lightbulb:
ScopedValue.where(USER, user).call(body)把USER绑定为user、在body作用域内可见,call返回后绑定自动解除。ScopedValue在java.lang,无需 import。
运行输出(JDK 25 实测):
running as alice
bound outside = false
与 JDK 8 对比#
static final ThreadLocal<String> USER = new ThreadLocal<>();
USER.set("alice"); // 可变:随时改
try { run(); }
finally { USER.remove(); } // 必须手动 remove,否则泄漏
String u = USER.get();
static final ScopedValue<String> USER = ScopedValue.newInstance();
ScopedValue.where(USER, "alice").run(() -> { // 不可变,作用域结束自动消失
String u = USER.get();
});
// 离开作用域:USER.isBound() == false,无需 remove
底层原理#
ScopedValue 的绑定是词法作用域、不可变的:where(V, x).run/call(body) 在进入 body 时建立绑定、退出时(无论正常返回还是抛异常)撤销。作用域内 V.get() 返回绑定值;作用域外 V.isBound() 为 false,V.get() 抛 NoSuchElementException。因为没有可变状态,多个虚拟线程可安全共享同一个 ScopedValue,且绑定随调用栈自动清理,不会泄漏。
flowchart TD
A["ScopedValue.where(USER, 'alice')"] --> B["进入作用域:USER 绑定"]
B --> C["body 内 USER.get() = 'alice'"]
C --> D{"作用域结束"}
D --> E["自动解绑:USER.isBound() = false"]
D -.body 抛异常.- E
style A fill:#bbdefb
style E fill:#ffe0b2
常见坑 / 最佳实践#
- 不可变:
ScopedValue一旦在作用域内绑定就不能改;要"覆盖"用嵌套的where再绑一层。 - 取未绑定值会抛:作用域外
get()抛NoSuchElementException;不确定时先用isBound()。 - 优先替代 ThreadLocal:新代码、尤其配合虚拟线程,优先
ScopedValue;遗留库的ThreadLocal仍可用,但别在新设计中扩散。 runvscall:run(Runnable)无返回值、call(Callable)有返回值(可抛受检异常)。
小结#
ScopedValue 用"不可变 + 词法作用域"替代 ThreadLocal 的"可变 + 无界",与虚拟线程、结构化并发(JEP 505)形成配套,是 JDK 25 并发工具箱的关键一环。