Module java.base
Package java.lang

Class ScopedValue<T>

java.lang.Object
java.lang.ScopedValue<T>
类型参数:
T - 值的类型

public final class ScopedValue<T> extends Object
ScopedValue 是Java平台的预览API。
仅当启用预览功能时,程序才能使用ScopedValue
预览功能可能会在未来的版本中被移除,或升级为Java平台的永久功能。
可以安全高效地共享给方法而无需使用方法参数的值。

在Java编程语言中,数据通常通过方法参数传递给方法。数据可能需要通过许多方法序列传递到使用数据的方法。调用序列中的每个方法都需要声明参数,并且每个方法都可以访问数据。ScopedValue提供了一种方法,可以将数据传递给远程方法(通常是回调),而无需使用方法参数。实际上,ScopedValue是一个隐式方法参数。就好像调用序列中的每个方法都有一个额外的参数一样。没有任何方法声明该参数,只有能够访问ScopedValue对象的方法才能访问其值(数据)。ScopedValue使得可以安全地将数据从调用者传递到远程被调用者,通过一系列中间方法,这些方法不声明数据的参数,也无法访问数据。

ScopedValue API通过执行一个绑定到某个值的ScopedValue对象的方法来工作。该方法可能调用另一个方法,然后可能调用另一个方法。这些方法的展开执行定义了一个动态作用域。具有访问ScopedValue对象的代码可以读取其值。当原始方法正常完成或出现异常时,ScopedValue对象将恢复为未绑定状态。ScopedValue API支持使用绑定到值的ScopedValue执行Runnable.runCallable.callSupplier.get方法。

考虑以下示例,其中一个名为"NAME"的scoped value绑定到值"duke"以执行run方法。run方法又调用doSomething

    private static final ScopedValue<String> NAME = ScopedValue.newInstance();

    ScopedValue.runWhere(NAME, "duke", () -> doSomething());
直接或间接由doSomething执行的代码,具有访问字段NAME的权限,可以调用NAME.get()来读取值"duke"。在执行run方法时,NAME被绑定。当run方法完成时,它将恢复为未绑定状态。

使用runWhere的示例调用一个不返回结果的方法。可以使用callWheregetWhere来调用返回结果的方法。此外,ScopedValue定义了where(ScopedValue, Object)方法,用于在调用所有绑定到值的ScopedValue的方法之前累积多个映射的情况。

绑定是每个线程的

绑定到值的ScopedValue是每个线程的。调用xxxWhere会使当前线程的方法执行时,ScopedValue绑定到一个值。get方法返回当前线程绑定的值。

在示例中,如果一个线程执行的代码调用了这个:

    ScopedValue.runWhere(NAME, "duke1", () -> doSomething());
另一个线程执行的代码调用了:
    ScopedValue.runWhere(NAME, "duke2", () -> doSomething());
那么由doSomething(或任何它调用的方法)执行的代码调用NAME.get()将读取值"duke1"或"duke2",取决于哪个线程正在执行。

作为能力的Scoped values

ScopedValue绑定时,应将其视为能力或访问其值的关键。安全使用取决于访问控制(参见Java虚拟机规范,第5.4.4节)并注意不要共享ScopedValue对象。在许多情况下,ScopedValue将被声明为finalstatic字段,以便只有单个类(或嵌套类)中的代码可以访问它。

重新绑定

ScopedValue API允许为嵌套动态作用域建立新的绑定。这称为重新绑定。绑定到值的ScopedValue可以在新方法的执行期间绑定到新值。由该方法执行的代码的展开定义了嵌套动态作用域。当方法完成时,ScopedValue的值将恢复为先前的值。

在上面的示例中,假设由doSomething执行的代码使用以下方式将NAME绑定到新值:

    ScopedValue.runWhere(NAME, "duchess", () -> doMore());
直接或间接由doMore()执行的代码调用NAME.get()将读取值"duchess"。当doMore()完成时,NAME的值将恢复为"duke"。

继承

ScopedValue支持跨线程共享。这种共享仅限于结构化情况,其中子线程在父线程的有界执行期间启动并终止。在使用StructuredTaskScope预览时,scoped value绑定在创建StructuredTaskScope时被捕获,并由使用fork预览方法启动的所有线程继承。

跨线程共享的ScopedValue要求值是不可变对象,或者所有对值的访问都得到适当同步。

在以下示例中,ScopedValue NAME绑定到值"duke"以运行一个可运行操作。在run方法中的代码创建了一个StructuredTaskScope,该作用域分叉了三个任务。由这些线程运行childTask1()childTask2()childTask3()直接或间接执行的代码调用NAME.get()将读取值"duke"。

    private static final ScopedValue<String> NAME = ScopedValue.newInstance();

    ScopedValue.runWhere(NAME, "duke", () -> {
        try (var scope = new StructuredTaskScope<String>()) {

            scope.fork(() -> childTask1());
            scope.fork(() -> childTask2());
            scope.fork(() -> childTask3());

            ...
         }
    });

除非另有说明,向此类中的方法传递null参数将导致抛出NullPointerException

API注释:
在需要在不使用方法参数的情况下进行“单向传输”数据时,应优先选择ScopedValue而不是ThreadLocal。虽然ThreadLocal可以用于在不使用方法参数的情况下向方法传递数据,但它存在一些问题:
  1. ThreadLocal无法阻止远程调用者在调用时设置新值。
  2. ThreadLocal具有无限生命周期,因此在方法完成后仍然具有值,除非显式移除
  3. 继承是昂贵的 - 每次创建子线程时,必须复制线程本地值到值的映射。
实现注释:
Scoped values设计用于在相对较小的数量下使用。get()最初通过封闭作用域进行搜索,以找到作用域值的最内部绑定。然后将搜索结果缓存在一个小的线程本地缓存中。对于该作用域值的后续调用get()几乎总是非常快的。但是,如果程序循环使用许多作用域值,缓存命中率将很低,性能将很差。这种设计允许通过StructuredTaskScope预览线程快速继承作用域值:实质上,不过是复制一个指针,并且保留作用域值绑定也只需要更新一个指针。

由于作用域值每个线程的缓存很小,客户端应该尽量减少使用绑定的作用域值的数量。例如,如果需要以这种方式传递多个值,最好创建一个记录类来保存这些值,然后将一个ScopedValue绑定到该记录类的实例。

对于此版本,参考实现提供了一些系统属性来调整作用域值的性能。

系统属性java.lang.ScopedValue.cacheSize控制(每线程)作用域值缓存的大小。该缓存对作用域值的性能至关重要。如果太小,运行时库将不断需要扫描每个get()。如果太大,将不必要地消耗内存。默认的作用域值缓存大小为16个条目。可以将ScopedValue.cacheSize的大小变化为2到16个条目。ScopedValue.cacheSize必须是2的整数次幂。

例如,您可以使用-Djava.lang.ScopedValue.cacheSize=8

另一个系统属性是jdk.preserveScopedValueCache。此属性确定当虚拟线程被阻塞时是否保留每线程的作用域值缓存。默认情况下,此属性设置为true,这意味着每个虚拟线程在被阻塞时保留其作用域值缓存。与ScopedValue.cacheSize一样,这是空间与速度的权衡:在大多数时间内许多虚拟线程被阻塞的情况下,将此属性设置为false可能会节省一些内存,但每个虚拟线程的作用域值缓存在阻塞操作后必须重新生成。

自版本:
21
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static final class 
    预览。
    作为的作用域值到值的映射。
  • Method Summary

    Modifier and Type
    Method
    Description
    static <T, R> R
    callWhere(ScopedValuePREVIEW<T> key, T value, Callable<? extends R> op)
    使用当前线程中绑定到值的ScopedValue调用返回值操作。
    T
    get()
    如果当前线程中绑定了作用域值,则返回该作用域值的值。
    static <T, R> R
    getWhere(ScopedValuePREVIEW<T> key, T value, Supplier<? extends R> op)
    使用当前线程中绑定到值的ScopedValue调用结果供应商。
    boolean
    如果当前线程中绑定了此作用域值,则返回true
    static <T> ScopedValuePREVIEW<T>
    创建一个最初对所有线程未绑定的作用域值。
    T
    orElse(T other)
    如果当前线程中绑定了此作用域值,则返回该作用域值的值,否则返回other
    <X extends Throwable>
    T
    orElseThrow(Supplier<? extends X> exceptionSupplier)
    如果当前线程中绑定了此作用域值,则返回该作用域值的值,否则抛出由异常提供函数产生的异常。
    static <T> void
    runWhere(ScopedValuePREVIEW<T> key, T value, Runnable op)
    使用当前线程中绑定到值的ScopedValue运行操作。
    where(ScopedValuePREVIEW<T> key, T value)
    创建一个新的Carrier,其中包含一个ScopedValue 到值的映射。

    Methods declared in class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • where

      public static <T> ScopedValue.CarrierPREVIEW where(ScopedValuePREVIEW<T> key, T value)
      创建一个新的Carrier,其中包含一个ScopedValue 到值的映射。可以使用Carrier累积映射,以便可以执行所有映射中绑定到值的作用域值的操作。以下示例运行一个将k1绑定(或重新绑定)到v1,并将k2绑定(或重新绑定)到v2的操作。
          ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
      
      类型参数:
      T - 值的类型
      参数:
      key - ScopedValue
      value - 值,可以为null
      返回:
      一个包含单个映射的新Carrier
    • callWhere

      public static <T, R> R callWhere(ScopedValuePREVIEW<T> key, T value, Callable<? extends R> op) throws Exception
      使用当前线程中绑定到值的ScopedValue调用返回值操作。当操作完成(正常或异常完成)时,ScopedValue将在当前线程中恢复为未绑定状态,或在先前绑定时恢复为先前的值。如果op完成时出现异常,则此方法将传播异常。

      作用域值应以结构化方式使用。如果由操作直接或间接调用的代码创建了一个StructuredTaskScope预览但未关闭预览它,则当操作完成(正常或异常完成)时,将检测到结构违规。在这种情况下,StructuredTaskScope的基础结构将被关闭,并抛出StructureViolationException预览

      实现注释:
      此方法实现等效于:
          ScopedValue.where(key, value).call(op);
      
      类型参数:
      T - 值的类型
      R - 结果类型
      参数:
      key - ScopedValue
      value - 值,可以为null
      op - 要调用的操作
      返回:
      结果
      抛出:
      StructureViolationException预览 - 如果检测到结构违规
      Exception - 如果操作完成时出现异常
    • getWhere

      public static <T, R> R getWhere(ScopedValuePREVIEW<T> key, T value, Supplier<? extends R> op)
      使用当前线程中绑定到值的ScopedValue调用结果供应商。当操作完成(正常或异常完成)时,ScopedValue将在当前线程中恢复为未绑定状态,或在先前绑定时恢复为先前的值。如果op完成时出现异常,则此方法将传播异常。

      作用域值应以结构化方式使用。如果由操作直接或间接调用的代码创建了一个StructuredTaskScope预览但未关闭预览它,则当操作完成(正常或异常完成)时,将检测到结构违规。在这种情况下,StructuredTaskScope的基础结构将被关闭,并抛出StructureViolationException预览

      实现说明:
      此方法的实现等效于:
          ScopedValue.where(key, value).get(op);
      
      类型参数:
      T - 值的类型
      R - 结果类型
      参数:
      key - ScopedValue 的键
      value - 值,可以为 null
      op - 要调用的操作
      返回:
      结果
      抛出:
      StructureViolationException预览 - 如果检测到结构违规
    • runWhere

      public static <T> void runWhere(ScopedValuePREVIEW<T> key, T value, Runnable op)
      使用绑定到当前线程中的值的 ScopedValue 运行操作。当操作完成(正常或异常完成)时,ScopedValue 将恢复为未绑定状态,或者在之前绑定时将恢复为先前的值。如果 op 完成时出现异常,则此方法会传播异常。

      Scoped values 应以结构化方式使用。如果操作直接或间接调用的代码创建了一个 StructuredTaskScope预览,但未关闭预览它,则在操作完成(正常或异常完成)时将检测到结构违规。在这种情况下,将关闭 StructuredTaskScope 的基础构造,并抛出 StructureViolationException预览

      实现说明:
      此方法的实现等效于:
          ScopedValue.where(key, value).run(op);
      
      类型参数:
      T - 值的类型
      参数:
      key - ScopedValue 的键
      value - 值,可以为 null
      op - 要调用的操作
      抛出:
      StructureViolationException预览 - 如果检测到结构违规
    • newInstance

      public static <T> ScopedValuePREVIEW<T> newInstance()
      创建一个最初对所有线程未绑定的作用域值。
      类型参数:
      T - 值的类型
      返回:
      一个新的 ScopedValue
    • get

      public T get()
      如果在当前线程中绑定了作用域值,则返回其值。
      返回:
      如果在当前线程中绑定了作用域值,则返回其值
      抛出:
      NoSuchElementException - 如果作用域值未绑定
    • isBound

      public boolean isBound()
      如果此作用域值在当前线程中绑定,则返回true
      返回:
      如果此作用域值在当前线程中绑定,则返回true
    • orElse

      public T orElse(T other)
      如果此作用域值在当前线程中绑定,则返回其值,否则返回other
      参数:
      other - 如果未绑定,则返回的值,可以为 null
      返回:
      如果绑定,则返回作用域值的值,否则返回other
    • orElseThrow

      public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
      如果此作用域值在当前线程中绑定,则返回其值,否则抛出由异常提供函数生成的异常。
      类型参数:
      X - 可能抛出的异常类型
      参数:
      exceptionSupplier - 生成要抛出的异常的提供函数
      返回:
      如果在当前线程中绑定了作用域值,则返回其值
      抛出:
      X - 如果在当前线程中未绑定作用域值