- 类型参数:
T- 绑定到此ScopedValue的对象的类型
ScopedValue 允许在有限的执行时间内安全有效地共享数据,而无需将数据作为方法参数传递。
ScopedValue 定义了 where(ScopedValue, Object, Runnable) 方法来为 runnable 的 run 方法的线程设置 ScopedValue 的有界执行周期的值。 run 执行的方法的展开执行定义了一个dynamic scope .在动态范围内执行时,作用域值为 bound,当 run 方法完成时(正常或出现异常),它恢复为 unbound。在动态范围内执行的代码使用 ScopedValue get 方法读取其值。
像 线程局部变量 一样,作用域值有多个化身,每个线程一个。使用的特定化身取决于调用其方法的线程。
考虑以下示例,其作用域值 USERNAME 是 bound 到值“duke”,用于线程执行调用 doSomething() 的运行方法。
private static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
ScopedValue.where(USERNAME, "duke", () -> doSomething());
doSomething() 直接或间接执行的调用 USERNAME.get() 的代码将读取值“duke”。作用域值在执行 doSomething() 时绑定,并在 doSomething() 完成(正常或异常)时解除绑定。如果一个线程调用 doSomething()并将USERNAME绑定到“duke1”,而另一个线程调用方法并将USERNAME绑定到“duke2”,则USERNAME.get()将读取值“duke1”或“duke2”,具体取决于哪个线程是执行。
除了执行run方法的where方法外, ScopedValue定义了where(ScopedValue, Object, Callable) 方法来执行返回结果的方法。它还定义了 where(ScopedValue, Object) 方法,用于将 ScopedValue 的映射累积到值的情况。
ScopedValue 通常会在 final 和 static 字段中声明。该字段的可访问性将决定哪些组件可以绑定或读取它的值。
除非另有说明,否则将 null 参数传递给此类中的方法将导致抛出 NullPointerException 。
重新绑定
ScopedValue API 允许为 nested dynamic scopes 建立新的绑定。这被称为 rebinding 。绑定到某个值的 ScopedValue 可能会绑定到一个新值,以用于某些方法的有界执行。由该方法执行的代码的展开执行定义了嵌套的动态范围。当该方法完成时(正常或出现异常),ScopedValue 的值将恢复为其先前的值。
在上面的示例中,假设 doSomething() 执行的代码将 USERNAME 绑定到一个新值:
ScopedValue.where(USERNAME, "duchess", () -> doMore());
doMore() 直接或间接执行的调用 USERNAME.get() 的代码将读取值“duchess”。当 doMore() 完成(正常或异常)时,USERNAME 的值恢复为“duke”。
遗产
ScopedValue 支持跨线程共享数据。这种共享仅限于子线程在父线程执行的有限时间内启动和终止的结构化情况。更具体地说,当使用 StructuredTaskScope 时,作用域值绑定在创建 StructuredTaskScope 时是 captured 并由在该作用域中使用 fork 方法启动的所有线程继承。
在以下示例中,ScopedValue USERNAME 绑定到值“duke”以执行可运行操作。 run 方法中的代码创建一个 StructuredTaskScope 并分叉三个子线程。由这些运行 childTask1()、childTask2() 和 childTask3() 的线程直接或间接执行的代码将读取值“duke”。
private static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
ScopedValue.where(USERNAME, "duke", () -> {
try (var scope = new StructuredTaskScope<String>()) {
scope.fork(() -> childTask1());
scope.fork(() -> childTask2());
scope.fork(() -> childTask3());
...
}
});
- 实现注意事项:
-
作用域值被设计为以相当小的数量使用。
get()最初通过封闭范围执行搜索以查找范围值的最内层绑定。然后它将搜索结果缓存在一个小的线程本地缓存中。对该作用域值的get()的后续调用几乎总是非常快。但是,如果一个程序有很多循环使用的作用域值,缓存命中率就会很低,性能也会很差。这种设计允许StructuredTaskScope线程的作用域值继承非常快:本质上,只需要复制一个指针,而留下一个作用域值绑定也只需要更新一个指针。因为每线程缓存的作用域值很小,所以客户端应该尽量减少使用中的绑定作用域值的数量。例如,如果需要以这种方式传递多个值,则创建一个记录类来保存这些值,然后将单个
ScopedValue绑定到该记录的实例是有意义的。对于这个孵化器版本,参考实现提供了一些系统属性来调整作用域值的性能。
系统属性
jdk.incubator.concurrent.ScopedValue.cacheSize控制(每线程)作用域值缓存的大小。此缓存对于作用域值的性能至关重要。如果它太小,运行时库将需要反复扫描每个get()。如果它太大,内存将被不必要地消耗。默认范围值高速缓存大小为 16 个条目。它的大小可能从 2 到 16 个条目不等。ScopedValue.cacheSize必须是 2 的整数次幂。例如,您可以使用
-Djdk.incubator.concurrent.ScopedValue.cacheSize=8。另一个系统属性是
jdk.preserveScopedValueCache。此属性确定在虚拟线程被阻塞时是否保留每线程作用域值缓存。默认情况下,此属性设置为true,这意味着每个虚拟线程在被阻塞时都会保留其作用域值缓存。与ScopedValue.cacheSize一样,这是空间与速度的权衡:在许多虚拟线程大部分时间被阻塞的情况下,将此属性设置为false可能会节省有用的内存,但每个虚拟线程的作用域值缓存必须阻塞操作后重新生成。 - 自从:
- 20
-
内部类总结
内部类 -
方法总结
修饰符和类型方法描述get()如果在当前线程中绑定,则返回作用域值的值。booleanisBound()如果此作用域值绑定在当前线程中,则返回true。static <T> ScopedValue<T>创建一个范围内的值,该值最初对所有线程都是未绑定的。如果在当前线程中绑定,则返回此作用域值的值,否则返回other。orElseThrow(Supplier<? extends X> exceptionSupplier) 如果在当前线程中绑定,则返回此作用域值的值,否则抛出由异常提供函数产生的异常。static <T> ScopedValue.Carrierwhere(ScopedValue<T> key, T value) 使用ScopedValuekey 到值的单个映射创建一个新的Carrier。static <T> voidwhere(ScopedValue<T> key, T value, Runnable op) 使用绑定到当前线程中的值的ScopedValue运行操作。static <T,R> R where(ScopedValue<T> key, T value, Callable<? extends R> op) 使用绑定到当前线程中的值的ScopedValue调用值返回操作。
-
方法详情
-
where
使用ScopedValuekey 到值的单个映射创建一个新的Carrier。Carrier可用于累积映射,以便可以使用绑定到值的map中的所有作用域值执行操作。以下示例运行一个操作,其中k1绑定(或反弹)到v1,k2绑定(或反弹)到v2。ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );- 类型参数:
T- 值的类型- 参数:
key-ScopedValue键value- 值,可以是null- 返回:
-
具有单个映射的新
Carrier
-
where
public static <T,R> R where(ScopedValue <T> key, T value, Callable <? extends R> op) throws Exception 使用绑定到当前线程中的值的ScopedValue调用值返回操作。当操作完成时(正常或异常),ScopedValue将在当前线程中恢复为未绑定状态,或恢复为先前绑定时的先前值。作用域值旨在用于 structured manner 。如果
op创建了一个StructuredTaskScope但没有创建 close 它,那么退出op会导致在动态范围内创建的每个StructuredTaskScope的底层结构被关闭。这可能需要阻塞,直到所有子线程都完成了它们的子任务。关闭是按照创建它们的相反顺序完成的。一旦关闭,StructureViolationException就会被抛出。 -
where
使用绑定到当前线程中的值的ScopedValue运行操作。当操作完成时(正常或异常),ScopedValue将在当前线程中恢复为未绑定状态,或恢复为先前绑定时的先前值。作用域值旨在用于 structured manner 。如果
op创建了一个StructuredTaskScope但没有创建 close 它,那么退出op会导致在动态范围内创建的每个StructuredTaskScope的底层结构被关闭。这可能需要阻塞,直到所有子线程都完成了它们的子任务。关闭是按照创建它们的相反顺序完成的。一旦关闭,StructureViolationException就会被抛出。- 实现注意事项:
-
此方法的实现等效于:
ScopedValue.where(key, value).run(op); - 类型参数:
T- 值的类型- 参数:
key-ScopedValue键value- 值,可以是nullop- 要调用的操作
-
newInstance
创建一个范围内的值,该值最初对所有线程都是未绑定的。- 类型参数:
T- 值的类型- 返回:
-
一个新的
ScopedValue
-
get
如果在当前线程中绑定,则返回作用域值的值。- 返回:
- 如果在当前线程中绑定,则为作用域值的值
- 抛出:
NoSuchElementException- 如果范围值未绑定
-
isBound
public boolean isBound()如果此作用域值绑定在当前线程中,则返回true。- 返回:
true如果此作用域值绑定在当前线程中
-
orElse
如果在当前线程中绑定,则返回此作用域值的值,否则返回other。- 参数:
other- 未绑定时返回的值,可以是null- 返回:
-
如果绑定,则为范围值的值,否则为
other
-
orElseThrow
如果在当前线程中绑定,则返回此作用域值的值,否则抛出由异常提供函数产生的异常。- 类型参数:
X- 可能抛出的异常类型- 参数:
exceptionSupplier- 产生要抛出的异常的提供函数- 返回:
- 如果在当前线程中绑定,则为作用域值的值
- 抛出:
X- 如果作用域值未绑定在当前线程中
-