Class ScopedValue<T>
- Type Parameters:
T
- the type of the value
ScopedValue
is a preview API of the Java platform.ScopedValue
when preview features are enabled. 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 acallback) without using method parameters. In effect, aScopedValue
is animplicit 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 theScopedValue
object can access its value (the data).ScopedValue
makes it possible to securely pass data from acaller to a farawaycallee through a sequence of intermediate methods that do not declare a parameter for the data and have no access to the data.
TheScopedValue
API works by executing a method with aScopedValue
objectbound 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 adynamic scope. Code in these methods with access to theScopedValue
object may read its value. TheScopedValue
object reverts to beingunbound when the original method completes normally or with an exception. TheScopedValue
API supports executing aRunnable
, orScopedValue.CallableOp
PREVIEW with aScopedValue
bound to a value.
Consider the following example with a scoped value "NAME
" bound to the value "duke
" for the execution of aRunnable
'srun
method. Therun
method, in turn, invokes a methoddoSomething
.
private static final ScopedValue<String> NAME = ScopedValue.newInstance
(); ScopedValue.where(NAME, "duke").run
(() -> doSomething());
doSomething
, with access to the fieldNAME
, can invokeNAME.get()
to read the value "duke
". NAME
is bound while executing therun
method. It reverts to being unbound when therun
method completes. The example usingrun
invokes a method that does not return a result. Thecall
PREVIEW method can be used to invoke a method that returns a result.ScopedValue
defines thewhere(ScopedValue, Object)
method for cases where multiple mappings (ofScopedValue
to value) are accumulated in advance of calling a method with allScopedValue
s bound to their value.
Bindings are per-thread
AScopedValue
binding to a value is per-thread. Invokingrun
executes a method with aScopedValue
bound to a value for the current thread. Theget
method returns the value bound for the current thread.In the example, if code executed by one thread invokes this:
ScopedValue.where(NAME, "duke1").run(() -> doSomething());
ScopedValue.where(NAME, "duke2").run(() -> doSomething());
doSomething
(or any method that it calls) invokingNAME.get()
will read the value "duke1
" or "duke2
", depending on which thread is executing.Scoped values as capabilities
AScopedValue
object should be treated as acapability or a key to access its value when theScopedValue
is bound. Secure usage depends on access control (seeThe Java Virtual Machine Specification, Section5.4.4) and taking care to not share theScopedValue
object. In many cases, a ScopedValue
will be declared in afinal
andstatic
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 fornested dynamic scopes. This is known asrebinding. AScopedValue
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 theScopedValue
reverts to its previous value. In the above example, suppose that code executed bydoSomething
bindsNAME
to a new value with:
ScopedValue.where(NAME, "duchess").run(() -> doMore());
doMore()
that invokes NAME.get()
will read the value "duchess
". WhendoMore()
completes then the value ofNAME
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 aStructuredTaskScope
PREVIEW, scoped value bindings arecaptured when creating aStructuredTaskScope
and inherited by all threads started in that task scope with thefork
PREVIEW method. AScopedValue
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, theScopedValue
NAME
is bound to the value "duke
" for the execution of a runnable operation. The code in the run
method creates aStructuredTaskScope
that forks three tasks. Code executed directly or indirectly by these threads runningchildTask1()
,childTask2()
, andchildTask3()
that invokesNAME.get()
will read the value "duke
".
private static final ScopedValue<String> NAME = ScopedValue.newInstance(); ScopedValue.where(NAME, "duke").run(() -> { try (var scope = new StructuredTaskScope<String>()) { scope.fork
(() -> childTask1()); scope.fork(() -> childTask2()); scope.fork(() -> childTask3()); scope.join
(); .. } });
Unless otherwise specified, passing anull
argument to a method in this class will cause aNullPointerException
to be thrown.
- API Note:
- A
ScopedValue
should be preferred over aThreadLocal
for cases where the goal is "one-way transmission" of data without using method parameters. While aThreadLocal
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 fromsetting a new value.- A
ThreadLocal
has an unbounded lifetime and thus continues to have a value after a method completes, unless explicitlyremoved. - 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 byStructuredTaskScope
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 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.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 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 tofalse
might 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 interface
ScopedValue.CallableOpPREVIEW<T,X extendsThrowable>
Preview.An operation that returns a result and may throw an exception.static final class
Preview.A mapping of scoped values, askeys, to values.Method Summary
Modifier and TypeMethodDescriptionget()
Returns the value of the scoped value if bound in the current thread.boolean
isBound()
Returnstrue
if 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> ScopedValue.CarrierPREVIEW
where
(ScopedValuePREVIEW<T> key, T value) Creates a newCarrier
with a single mapping of aScopedValue
key to a value.
Method Details
where
Creates a newCarrier
with a single mapping of aScopedValue
key to a value. TheCarrier
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 withk1
bound (or rebound) tov1
, andk2
bound (or rebound) tov2
.ScopedValue.where(k1, v1).where(k2, v2).
run
(() -> ... );- Type Parameters:
T
- the type of the value- Parameters:
key
- theScopedValue
keyvalue
- the value, can benull
- Returns:
- a new
Carrier
with a single mapping
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()Returnstrue
if this scoped value is bound in the current thread.- Returns:
true
if this scoped value is bound in the current thread
orElse
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