- 类型参数:
-
T
- 值的类型
ScopedValue
是Java平台的预览API。
在Java编程语言中,数据通常通过方法参数传递给方法。数据可能需要通过许多方法序列传递到使用数据的方法。调用序列中的每个方法都需要声明参数,并且每个方法都可以访问数据。ScopedValue
提供了一种方法,可以将数据传递给远程方法(通常是回调),而无需使用方法参数。实际上,ScopedValue
是一个隐式方法参数。就好像调用序列中的每个方法都有一个额外的参数一样。没有任何方法声明该参数,只有能够访问ScopedValue
对象的方法才能访问其值(数据)。ScopedValue
使得可以安全地将数据从调用者传递到远程被调用者,通过一系列中间方法,这些方法不声明数据的参数,也无法访问数据。
ScopedValue
API通过执行一个绑定到某个值的ScopedValue
对象的方法来工作。该方法可能调用另一个方法,然后可能调用另一个方法。这些方法的展开执行定义了一个动态作用域。具有访问ScopedValue
对象的代码可以读取其值。当原始方法正常完成或出现异常时,ScopedValue
对象将恢复为未绑定状态。ScopedValue
API支持使用绑定到值的ScopedValue
执行Runnable.run
、Callable.call
或Supplier.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
的示例调用一个不返回结果的方法。可以使用callWhere
和getWhere
来调用返回结果的方法。此外,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
将被声明为final
和static
字段,以便只有单个类(或嵌套类)中的代码可以访问它。
重新绑定
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
可以用于在不使用方法参数的情况下向方法传递数据,但它存在一些问题: - 实现注释:
-
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
Modifier and TypeClassDescriptionstatic final class
预览。作为键的作用域值到值的映射。 -
Method Summary
Modifier and TypeMethodDescriptionstatic <T,
R> R callWhere
(ScopedValuePREVIEW<T> key, T value, Callable<? extends R> op) 使用当前线程中绑定到值的ScopedValue
调用返回值操作。get()
如果当前线程中绑定了作用域值,则返回该作用域值的值。static <T,
R> R getWhere
(ScopedValuePREVIEW<T> key, T value, Supplier<? extends R> op) 使用当前线程中绑定到值的ScopedValue
调用结果供应商。boolean
isBound()
如果当前线程中绑定了此作用域值,则返回true
。static <T> ScopedValuePREVIEW
<T> 创建一个最初对所有线程未绑定的作用域值。如果当前线程中绑定了此作用域值,则返回该作用域值的值,否则返回other
。orElseThrow
(Supplier<? extends X> exceptionSupplier) 如果当前线程中绑定了此作用域值,则返回该作用域值的值,否则抛出由异常提供函数产生的异常。static <T> void
runWhere
(ScopedValuePREVIEW<T> key, T value, Runnable op) 使用当前线程中绑定到值的ScopedValue
运行操作。static <T> ScopedValue.CarrierPREVIEW
where
(ScopedValuePREVIEW<T> key, T value) 创建一个新的Carrier
,其中包含一个ScopedValue
键到值的映射。
-
Method Details
-
where
创建一个新的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
使用当前线程中绑定到值的ScopedValue
调用结果供应商。当操作完成(正常或异常完成)时,ScopedValue
将在当前线程中恢复为未绑定状态,或在先前绑定时恢复为先前的值。如果op
完成时出现异常,则此方法将传播异常。作用域值应以结构化方式使用。如果由操作直接或间接调用的代码创建了一个
StructuredTaskScope
预览但未关闭预览它,则当操作完成(正常或异常完成)时,将检测到结构违规。在这种情况下,StructuredTaskScope
的基础结构将被关闭,并抛出StructureViolationException
预览。- 实现说明:
-
此方法的实现等效于:
ScopedValue.where(key, value).
get
(op); - 类型参数:
-
T
- 值的类型 -
R
- 结果类型 - 参数:
-
key
-ScopedValue
的键 -
value
- 值,可以为null
-
op
- 要调用的操作 - 返回:
- 结果
- 抛出:
-
StructureViolationException预览
- 如果检测到结构违规
-
runWhere
使用绑定到当前线程中的值的ScopedValue
运行操作。当操作完成(正常或异常完成)时,ScopedValue
将恢复为未绑定状态,或者在之前绑定时将恢复为先前的值。如果op
完成时出现异常,则此方法会传播异常。Scoped values 应以结构化方式使用。如果操作直接或间接调用的代码创建了一个
StructuredTaskScope
预览,但未关闭预览它,则在操作完成(正常或异常完成)时将检测到结构违规。在这种情况下,将关闭StructuredTaskScope
的基础构造,并抛出StructureViolationException
预览。- 实现说明:
-
此方法的实现等效于:
ScopedValue.where(key, value).
run
(op); - 类型参数:
-
T
- 值的类型 - 参数:
-
key
-ScopedValue
的键 -
value
- 值,可以为null
-
op
- 要调用的操作 - 抛出:
-
StructureViolationException预览
- 如果检测到结构违规
-
newInstance
创建一个最初对所有线程未绑定的作用域值。- 类型参数:
-
T
- 值的类型 - 返回:
-
一个新的
ScopedValue
-
get
如果在当前线程中绑定了作用域值,则返回其值。- 返回:
- 如果在当前线程中绑定了作用域值,则返回其值
- 抛出:
-
NoSuchElementException
- 如果作用域值未绑定
-
isBound
public boolean isBound()如果此作用域值在当前线程中绑定,则返回true
。- 返回:
-
如果此作用域值在当前线程中绑定,则返回
true
-
orElse
如果此作用域值在当前线程中绑定,则返回其值,否则返回other
。- 参数:
-
other
- 如果未绑定,则返回的值,可以为null
- 返回:
-
如果绑定,则返回作用域值的值,否则返回
other
-
orElseThrow
如果此作用域值在当前线程中绑定,则返回其值,否则抛出由异常提供函数生成的异常。- 类型参数:
-
X
- 可能抛出的异常类型 - 参数:
-
exceptionSupplier
- 生成要抛出的异常的提供函数 - 返回:
- 如果在当前线程中绑定了作用域值,则返回其值
- 抛出:
-
X
- 如果在当前线程中未绑定作用域值
-
ScopedValue
。