跳到主要内容

04、Java 20 新特性 - 作用域值 Scoped Value(孵化器)

作用域值可以在线程内和线程之间共享不可变数据。它们比线程本地变量更好用,特别是使用大量虚拟线程时。

ThreadLocal

自Java 1.2 以来,我们可以使用 ThreadLocal 变量将某个值限制在创建它的线程中。在某些情况下,这可能是实现线程安全的简单方法。

但是,线程本地变量也有一些注意事项。每个线程本地变量都是可变的,这使得很难区分哪个组件更新了共享状态以及以何种顺序。还存在内存泄漏的风险,因为除非在 ThreadLocal 上调用 remove(),否则数据将保留直到垃圾回收(仅在线程终止后才会发生)。最后,父线程的线程本地变量可以被子线程继承,这导致子线程必须为在父线程中先前编写的每个线程本地变量分配存储。

现在虚拟线程被引入后,这些缺点变得更加明显,因为可能会有数百万个虚拟线程处于活动状态,即每个线程都有自己的线程本地变量,这将导致显着的内存占用。

作用域值

与线程本地变量类似,作用域值有多个实例,每个线程一个。不同之处在于,作用域值只写入一次,然后是不可变的,并且仅在线程执行期间的有界时间内可用。

以下是JEP 用伪代码示例说明如何使用作用域值:

final static ScopedValue < ... > V = ScopedValue.newInstance();
// In some method
        ScopedValue.where(V, < value > )
        .run(() - > { ...V.get()...call methods...
        });
// In a method called directly or indirectly from the lambda expression
        ...V.get()...

可以看到,调用 ScopedValue.where(...) 方法会返回一个作用域范围的值和一个要绑定到该值的对象。调用 run(...)方法会绑定该作用域的值,提供一个特定于当前线程的实例,然后执行作为参数传递的 lambda 表达式。在调用 run(...) 方法的生命周期内,lambda 表达式或任何直接或间接从该表达式调用的方法都可以通过值的 get() 方法读取作用域范围的值。在 run(...) 方法结束后,绑定会被销毁。

典型应用场景

作用域范围的值在所有当前使用单向传输不变数据的线程本地变量的地方都很有用。

与 Java 19 有什么不同

Java 19 中还没有任何与作用域范围的值相关的内容,因此 Java 20 是我们第一次尝试使用它们。

请注意:JEP 处于孵化器阶段,因此需要在命令行中添加 --enable-preview --add-modules jdk.incubator.concurrent 才能试用该功能。