在JDK 21(预览版)中使用作用域值增强Java并发性

Scoped Values 在 JDK 21 中的引入

Scoped Values 作为预览功能(Preview Feature)被引入 JDK 21,与虚拟线程(Virtual Threads)和结构化并发(Structured Concurrency)一起,进一步增强了 Java 和 Project Loom 的功能。

Scoped Values 的核心概念

Scoped Values 可以在任何地方访问,前提是已经创建了一个动态作用域,并且所需的值已经绑定到该作用域中。Scoped Values 的行为类似于调用链中每个方法的额外参数,但这些方法实际上并不声明该参数。只有访问到 ScopedValue 对象的方法才能检索其值,该值表示正在传递的数据。

Scoped Values 的优势

根据 JEP 446,Scoped Values 提高了安全性、不变性、封装性以及在线程内部和跨线程之间的高效访问。它们在多线程环境中使用事务、安全主体和其他形式的共享上下文时非常有用。然而,它们并不旨在取代 Java 1.2 中引入的 ThreadLocal 变量。

Scoped Values 与 ThreadLocal 的区别

ThreadLocal 变量是全局变量,通常声明为静态字段,具有访问器方法。这使得变量是可变的,因为 setter 方法可以更改其值。而 Scoped Values 通过控制共享数据,使其不可变并在作用域的生命周期内绑定,从而采取了不同的方法。

Scoped Values 的使用示例

Scoped Values 通过 ScopedValue 类的 newInstance() 工厂方法创建,并在 RunnableCallableSupplier 调用的上下文中赋值。以下示例展示了如何使用 Runnable

public class WithUserSession {
    private final static ScopedValue<String> USER_ID = new ScopedValue.newInstance();

    public void processWithUser(String sessionUserId) {
        ScopedValue.runWhere(USER_ID, sessionUserId, () -> processRequest());
    }
}

在这个示例中,USER_ID 是一个 Scoped Value,processWithUser 方法通过 runWhere 方法调用 processRequest 方法,并在该方法内绑定 sessionUserId 的值。

Scoped Values 的不可变性和线程共享

Scoped Values 是只读的,没有 set() 方法,确保其值在绑定后不可变。它们可以跨多个线程共享,如下例所示:

ScopedValue.runWhere(MULTIPLIER, number, () -> {
    try (var scope = new StructuredTaskScope<BigInteger>()) {
        scope.fork(() -> squaredByGet());
        scope.fork(() -> squaredByGet());
        scope.fork(() -> squaredByGet());
    }
});

Scoped Values 的缓存大小

Scoped Values 的缓存大小默认为 16 个条目,可以通过以下参数进行调整:

java.lang.ScopedValue.cacheSize

总结

Scoped Values 旨在解决 ThreadLocal 变量的局限性,特别是在虚拟线程的上下文中。虽然目前 ThreadLocal 仍然可以使用,但 Scoped Values 提供了一种更高效和安全的方式来在应用程序组件之间共享敏感信息。尽管 JDK 21 中 Scoped Values 仅作为预览功能引入,但它未来可能会逐步取代 ThreadLocal 的许多用例。更多详细信息可以参考 JEP-446 文档。

阅读 99
0 条评论