- Type Parameters:
T- the type of the value
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 ScopedValues bound to their value.
Bindings are per-thread
AScopedValue 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.
Scoped values as capabilities
AScopedValue 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).
Rebinding
TheScopedValue 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".
Inheritance
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 StructuredTaskScopePREVIEW,
scoped value bindings are captured when creating a StructuredTaskScope
and inherited by all threads started in that task scope with the
forkPREVIEW 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.
- API Note:
- A
ScopedValueshould be preferred over aThreadLocalfor cases where the goal is "one-way transmission" of data without using method parameters. While aThreadLocalcan be used to pass data to a method without using method parameters, it does suffer from a number of issues:-
ThreadLocaldoes not prevent code in a faraway callee from setting a new value. - A
ThreadLocalhas an unbounded lifetime and thus continues to have a value after a method completes, unless explicitly removed. - Inheritance is expensive - the map of thread-locals to values must be copied when creating each child thread.
-
- Implementation Note:
- Scoped values are designed to be used in fairly small
numbers.
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 ofget()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 byStructuredTaskScopePREVIEW 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
ScopedValueto 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.cacheSizecontrols 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 eachget(). 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.cacheSizemust 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 totrue, meaning that every virtual thread preserves its scoped-value cache when blocked. LikeScopedValue.cacheSize, this is a space versus speed trade-off: in situations where many virtual threads are blocked most of the time, setting this property tofalsemight result in a useful memory saving, but each virtual thread's scoped-value cache would have to be regenerated after a blocking operation. - Since:
- 21
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classPreview.A mapping of scoped values, as keys, to values. -
Method Summary
Modifier and TypeMethodDescriptionstatic <T,R> R callWhere(ScopedValuePREVIEW<T> key, T value, Callable<? extends R> op) Calls a value-returning operation with aScopedValuebound to a value in the current thread.get()Returns the value of the scoped value if bound in the current thread.static <T,R> R getWhere(ScopedValuePREVIEW<T> key, T value, Supplier<? extends R> op) Invokes a supplier of results with aScopedValuebound to a value in the current thread.booleanisBound()Returnstrueif this scoped value is bound in the current thread.static <T> ScopedValuePREVIEW<T> Creates a scoped value that is initially unbound for all threads.Returns the value of this scoped value if bound in the current thread, otherwise returnsother.orElseThrow(Supplier<? extends X> exceptionSupplier) 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> voidrunWhere(ScopedValuePREVIEW<T> key, T value, Runnable op) Run an operation with aScopedValuebound to a value in the current thread.static <T> ScopedValue.CarrierPREVIEWwhere(ScopedValuePREVIEW<T> key, T value) Creates a newCarrierwith a single mapping of aScopedValuekey to a value.
-
Method Details
-
where
Creates a newCarrierwith a single mapping of aScopedValuekey to a value. TheCarriercan 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 withk1bound (or rebound) tov1, andk2bound (or rebound) tov2.ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );- Type Parameters:
T- the type of the value- Parameters:
key- theScopedValuekeyvalue- the value, can benull- Returns:
- a new
Carrierwith a single mapping
-
callWhere
public static <T,R> R callWhere(ScopedValuePREVIEW<T> key, T value, Callable<? extends R> op) throws Exception Calls a value-returning operation with aScopedValuebound to a value in the current thread. When the operation completes (normally or with an exception), theScopedValuewill revert to being unbound, or revert to its previous value when previously bound, in the current thread. Ifopcompletes 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
StructuredTaskScopePREVIEW 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 theStructuredTaskScopeis closed andStructureViolationExceptionPREVIEW is thrown.- Implementation Note:
- This method is implemented to be equivalent to:
ScopedValue.where(key, value).call(op); - Type Parameters:
T- the type of the valueR- the result type- Parameters:
key- theScopedValuekeyvalue- the value, can benullop- the operation to call- Returns:
- the result
- Throws:
StructureViolationExceptionPREVIEW- if a structure violation is detectedException- if the operation completes with an exception
-
getWhere
Invokes a supplier of results with aScopedValuebound to a value in the current thread. When the operation completes (normally or with an exception), theScopedValuewill revert to being unbound, or revert to its previous value when previously bound, in the current thread. Ifopcompletes 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
StructuredTaskScopePREVIEW 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 theStructuredTaskScopeis closed andStructureViolationExceptionPREVIEW is thrown.- Implementation Note:
- This method is implemented to be equivalent to:
ScopedValue.where(key, value).get(op); - Type Parameters:
T- the type of the valueR- the result type- Parameters:
key- theScopedValuekeyvalue- the value, can benullop- the operation to call- Returns:
- the result
- Throws:
StructureViolationExceptionPREVIEW- if a structure violation is detected
-
runWhere
Run an operation with aScopedValuebound to a value in the current thread. When the operation completes (normally or with an exception), theScopedValuewill revert to being unbound, or revert to its previous value when previously bound, in the current thread. Ifopcompletes 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
StructuredTaskScopePREVIEW 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 theStructuredTaskScopeis closed andStructureViolationExceptionPREVIEW is thrown.- Implementation Note:
- This method is implemented to be equivalent to:
ScopedValue.where(key, value).run(op); - Type Parameters:
T- the type of the value- Parameters:
key- theScopedValuekeyvalue- the value, can benullop- the operation to call- Throws:
StructureViolationExceptionPREVIEW- if a structure violation is detected
-
newInstance
Creates a scoped value that is initially unbound for all threads.- Type Parameters:
T- the type of the value- Returns:
- a new
ScopedValue
-
get
Returns the value of the scoped value if bound in the current thread.- Returns:
- the value of the scoped value if bound in the current thread
- Throws:
NoSuchElementException- if the scoped value is not bound
-
isBound
public boolean isBound()Returnstrueif this scoped value is bound in the current thread.- Returns:
trueif this scoped value is bound in the current thread
-
orElse
Returns the value of this scoped value if bound in the current thread, otherwise returnsother.- Parameters:
other- the value to return if not bound, can benull- Returns:
- the value of the scoped value if bound, otherwise
other
-
orElseThrow
Returns the value of this scoped value if bound in the current thread, otherwise throws an exception produced by the exception supplying function.- Type Parameters:
X- the type of the exception that may be thrown- Parameters:
exceptionSupplier- the supplying function that produces the exception to throw- Returns:
- the value of the scoped value if bound in the current thread
- Throws:
X- if the scoped value is not bound in the current thread
-
ScopedValuewhen preview features are enabled.