T
- the type of the valuepublic final class ScopedValue<T> extends Object
ScopedValue
is a preview API of the Java platform. In the Java programming language, data is usually passed to a method by means of a method parameter. The data may need to be passed through a sequence of many methods to get to the method that makes use of the data. Every method in the sequence of calls needs to declare the parameter and every method has access to the data. ScopedValue
provides a means to pass data to a faraway method (typically a callback) without using method parameters. In effect, a ScopedValue
is an implicit method parameter. It is "as if" every method in a sequence of calls has an additional parameter. None of the methods declare the parameter and only the methods that have access to the ScopedValue
object can access its value (the data). ScopedValue
makes it possible to securely pass data from a caller to a faraway callee through a sequence of intermediate methods that do not declare a parameter for the data and have no access to the data.
The ScopedValue
API works by executing a method with a ScopedValue
object bound to some value for the bounded period of execution of a method. The method may invoke another method, which in turn may invoke another. The unfolding execution of the methods define a dynamic scope. Code in these methods with access to the ScopedValue
object may read its value. The ScopedValue
object reverts to being unbound when the original method completes normally or with an exception. The ScopedValue
API supports executing a Runnable.run
, Callable.call
, or Supplier.get
method with a ScopedValue
bound to a value.
Consider the following example with a scoped value "NAME
" bound to the value "duke
" for the execution of a run
method. The run
method, in turn, invokes doSomething
.
private static final ScopedValue<String> NAME = ScopedValue.newInstance
();
ScopedValue.runWhere
(NAME, "duke", () -> doSomething());
doSomething
, with access to the field NAME
, can invoke NAME.get()
to read the value "duke
".
NAME
is bound while executing the run
method. It reverts to being unbound when the run
method completes. The example using runWhere
invokes a method that does not return a result. The callWhere
and getWhere
can be used to invoke a method that returns a result. In addition, ScopedValue
defines the where(ScopedValue, Object)
method for cases where multiple mappings (of ScopedValue
to value) are accumulated in advance of calling a method with all ScopedValue
s bound to their value.
ScopedValue
binding to a value is per-thread. Invoking xxxWhere
executes a method with a ScopedValue
bound to a value for the current thread. The get
method returns the value bound for the current thread. In the example, if code executed by one thread invokes this:
ScopedValue.runWhere(NAME, "duke1", () -> doSomething());
ScopedValue.runWhere(NAME, "duke2", () -> doSomething());
doSomething
(or any method that it calls) invoking NAME.get()
will read the value "duke1
" or "duke2
", depending on which thread is executing. ScopedValue
object should be treated as a capability or a key to access its value when the ScopedValue
is bound. Secure usage depends on access control (see The Java Virtual Machine Specification, Section 5.4.4) and taking care to not share the ScopedValue
object. In many cases, a
ScopedValue
will be declared in a final
and static
field so that it is only accessible to code in a single class (or nest). ScopedValue
API allows a new binding to be established for nested dynamic scopes. This is known as rebinding. A ScopedValue
that is bound to a value may be bound to a new value for the bounded execution of a new method. The unfolding execution of code executed by that method defines the nested dynamic scope. When the method completes, the value of the ScopedValue
reverts to its previous value. In the above example, suppose that code executed by doSomething
binds NAME
to a new value with:
ScopedValue.runWhere(NAME, "duchess", () -> doMore());
doMore()
that invokes
NAME.get()
will read the value "duchess
". When doMore()
completes then the value of NAME
reverts to "duke
". ScopedValue
supports sharing across threads. This sharing is limited to structured cases where child threads are started and terminate within the bounded period of execution by a parent thread. When using a StructuredTaskScope
PREVIEW, scoped value bindings are captured when creating a StructuredTaskScope
and inherited by all threads started in that task scope with the fork
PREVIEW method. A ScopedValue
that is shared across threads requires that the value be an immutable object or for all access to the value to be appropriately synchronized.
In the following example, the ScopedValue
NAME
is bound to the value "duke
" for the execution of a runnable operation. The code in the
run
method creates a StructuredTaskScope
that forks three tasks. Code executed directly or indirectly by these threads running childTask1()
, childTask2()
, and childTask3()
that invokes NAME.get()
will read the value "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());
...
}
});
Unless otherwise specified, passing a null
argument to a method in this class will cause a NullPointerException
to be thrown.
ScopedValue
should be preferred over a ThreadLocal
for cases where the goal is "one-way transmission" of data without using method parameters. While a ThreadLocal
can be used to pass data to a method without using method parameters, it does suffer from a number of issues: ThreadLocal
does not prevent code in a faraway callee from setting a new value. ThreadLocal
has an unbounded lifetime and thus continues to have a value after a method completes, unless explicitly removed. get()
initially performs a search through enclosing scopes to find a scoped value's innermost binding. It then caches the result of the search in a small thread-local cache. Subsequent invocations of get()
for that scoped value will almost always be very fast. However, if a program has many scoped values that it uses cyclically, the cache hit rate will be low and performance will be poor. This design allows scoped-value inheritance by StructuredTaskScope
PREVIEW threads to be very fast: in essence, no more than copying a pointer, and leaving a scoped-value binding also requires little more than updating a pointer. Because the scoped-value per-thread cache is small, clients should minimize the number of bound scoped values in use. For example, if it is necessary to pass a number of values in this way, it makes sense to create a record class to hold those values, and then bind a single ScopedValue
to an instance of that record.
For this release, the reference implementation provides some system properties to tune the performance of scoped values.
The system property java.lang.ScopedValue.cacheSize
controls the size of the (per-thread) scoped-value cache. This cache is crucial for the performance of scoped values. If it is too small, the runtime library will repeatedly need to scan for each get()
. If it is too large, memory will be unnecessarily consumed. The default scoped-value cache size is 16 entries. It may be varied from 2 to 16 entries in size. ScopedValue.cacheSize
must be an integer power of 2.
For example, you could use -Djava.lang.ScopedValue.cacheSize=8
.
The other system property is jdk.preserveScopedValueCache
. This property determines whether the per-thread scoped-value cache is preserved when a virtual thread is blocked. By default this property is set to true
, meaning that every virtual thread preserves its scoped-value cache when blocked. Like
ScopedValue.cacheSize
, this is a space versus speed trade-off: in situations where many virtual threads are blocked most of the time, setting this property to false
might result in a useful memory saving, but each virtual thread's scoped-value cache would have to be regenerated after a blocking operation.
Modifier and Type | Class | Description |
---|---|---|
static final class |
ScopedValue.CarrierPREVIEW |
Preview. A mapping of scoped values, as keys, to values. |
Modifier and Type | Method | Description |
---|---|---|
static <T, |
callWhere |
Calls a value-returning operation with a ScopedValue bound to a value in the current thread. |
T |
get() |
Returns the value of the scoped value if bound in the current thread. |
static <T, |
getWhere |
Invokes a supplier of results with a ScopedValue bound to a value in the current thread. |
boolean |
isBound() |
Returns true if this scoped value is bound in the current thread. |
static <T> ScopedValuePREVIEW |
newInstance() |
Creates a scoped value that is initially unbound for all threads. |
T |
orElse |
Returns the value of this scoped value if bound in the current thread, otherwise returns other . |
<X extends Throwable> |
orElseThrow |
Returns the value of this scoped value if bound in the current thread, otherwise throws an exception produced by the exception supplying function. |
static <T> void |
runWhere |
Run an operation with a ScopedValue bound to a value in the current thread. |
static <T> ScopedValue.CarrierPREVIEW |
where |
Creates a new Carrier with a single mapping of a ScopedValue key to a value. |
public static <T> ScopedValue.CarrierPREVIEW where(ScopedValuePREVIEW<T> key, T value)
Carrier
with a single mapping of a ScopedValue
key to a value. The Carrier
can be used to accumulate mappings so that an operation can be executed with all scoped values in the mapping bound to values. The following example runs an operation with k1
bound (or rebound) to v1
, and k2
bound (or rebound) to v2
. ScopedValue.where(k1, v1).where(k2, v2).run
(() -> ... );
T
- the type of the valuekey
- the ScopedValue
keyvalue
- the value, can be null
Carrier
with a single mappingpublic static <T, R> R callWhere(ScopedValuePREVIEW<T> key, T value, Callable<? extends R> op) throws Exception
ScopedValue
bound to a value in the current thread. When the operation completes (normally or with an exception), the ScopedValue
will revert to being unbound, or revert to its previous value when previously bound, in the current thread. If op
completes with an exception then it propagated by this method. Scoped values are intended to be used in a structured manner. If code invoked directly or indirectly by the operation creates a StructuredTaskScope
PREVIEW but does not closePREVIEW it, then it is detected as a structure violation when the operation completes (normally or with an exception). In that case, the underlying construct of the StructuredTaskScope
is closed and StructureViolationException
PREVIEW is thrown.
ScopedValue.where(key, value).call
(op);
T
- the type of the valueR
- the result typekey
- the ScopedValue
keyvalue
- the value, can be null
op
- the operation to callStructureViolationExceptionPREVIEW
- if a structure violation is detectedException
- if the operation completes with an exceptionpublic static <T, R> R getWhere(ScopedValuePREVIEW<T> key, T value, Supplier<? extends R> op)
ScopedValue
bound to a value in the current thread. When the operation completes (normally or with an exception), the ScopedValue
will revert to being unbound, or revert to its previous value when previously bound, in the current thread. If op
completes with an exception then it propagated by this method. Scoped values are intended to be used in a structured manner. If code invoked directly or indirectly by the operation creates a StructuredTaskScope
PREVIEW but does not closePREVIEW it, then it is detected as a structure violation when the operation completes (normally or with an exception). In that case, the underlying construct of the StructuredTaskScope
is closed and StructureViolationException
PREVIEW is thrown.
ScopedValue.where(key, value).get
(op);
T
- the type of the valueR
- the result typekey
- the ScopedValue
keyvalue
- the value, can be null
op
- the operation to callStructureViolationExceptionPREVIEW
- if a structure violation is detectedpublic static <T> void runWhere(ScopedValuePREVIEW<T> key, T value, Runnable op)
ScopedValue
bound to a value in the current thread. When the operation completes (normally or with an exception), the ScopedValue
will revert to being unbound, or revert to its previous value when previously bound, in the current thread. If op
completes with an exception then it propagated by this method. Scoped values are intended to be used in a structured manner. If code invoked directly or indirectly by the operation creates a StructuredTaskScope
PREVIEW but does not closePREVIEW it, then it is detected as a structure violation when the operation completes (normally or with an exception). In that case, the underlying construct of the StructuredTaskScope
is closed and StructureViolationException
PREVIEW is thrown.
ScopedValue.where(key, value).run
(op);
T
- the type of the valuekey
- the ScopedValue
keyvalue
- the value, can be null
op
- the operation to callStructureViolationExceptionPREVIEW
- if a structure violation is detectedpublic static <T> ScopedValuePREVIEW<T> newInstance()
T
- the type of the valueScopedValue
public T get()
NoSuchElementException
- if the scoped value is not boundpublic boolean isBound()
true
if this scoped value is bound in the current thread.true
if this scoped value is bound in the current threadpublic T orElse(T other)
other
.other
- the value to return if not bound, can be null
other
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
X
- the type of the exception that may be thrownexceptionSupplier
- the supplying function that produces the exception to throwX
- if the scoped value is not bound in the current thread
© 1993, 2023, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ScopedValue.html
ScopedValue
when preview features are enabled.