- Type Parameters:
T
- the type of the object bound to thisScopedValue
ScopedValue
allows for safely and efficiently sharing
data for a bounded period of execution without passing the data as method arguments.
ScopedValue
defines the where(ScopedValue, Object, Runnable)
method to set the value of a ScopedValue
for the bouned period of execution by
a thread of the runnable's run
method. The unfolding execution of
the methods executed by run
defines a dynamic scope. The scoped
value is bound while executing in the dynamic scope, it reverts
to being unbound when the run
method completes (normally or with an
exception). Code executing in the dynamic scope uses the ScopedValue
get
method to read its value.
Like a thread-local variable, a scoped value has multiple incarnations, one per thread. The particular incarnation that is used depends on which thread calls its methods.
Consider the following example with a scoped value USERNAME
that is
bound to the value "duke
" for the execution, by a thread, of a run
method that invokes doSomething()
.
private static final ScopedValue<String> USERNAME = ScopedValue.newInstance
();
ScopedValue.where(USERNAME, "duke", () -> doSomething());
doSomething()
that invokes
USERNAME.get()
will read the value "duke
". The scoped value is bound while
executing doSomething()
and becomes unbound when doSomething()
completes (normally or with an exception). If one thread were to call
doSomething()
with USERNAME
bound to "duke1
", and another thread
were to call the method with USERNAME
bound to "duke2
", then
USERNAME.get()
would read the value "duke1
" or "duke2
",
depending on which thread is executing.
In addition to the where
method that executes a run
method,
ScopedValue
defines the where(ScopedValue, Object, Callable)
method to execute
a method that returns a result. It also defines the where(ScopedValue, Object)
method for cases where it is useful to accumulate mappings of ScopedValue
to
value.
A ScopedValue
will typically be declared in a final
and
static
field. The accessibility of the field will determine which components can
bind or read its value.
Unless otherwise specified, passing a null
argument to a method in this
class will cause a NullPointerException
to be thrown.
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 some value may be bound to a new value for the bounded execution of some
method. The unfolding execution of code executed by that method defines the nested
dynamic scope. When the method completes (normally or with an exception), the value of
the ScopedValue
reverts to its previous value.
In the above example, suppose that code executed by doSomething()
binds
USERNAME
to a new value with:
ScopedValue.where(USERNAME, "duchess", () -> doMore());
doMore()
that invokes
USERNAME.get()
will read the value "duchess
". When doMore()
completes
(normally or with an exception), the value of USERNAME
reverts to
"duke
".
Inheritance
ScopedValue
supports sharing data 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. More specifically, when using a StructuredTaskScope
, scoped value bindings are captured when creating a
StructuredTaskScope
and inherited by all threads started in that scope with
the fork
method.
In the following example, the ScopedValue
USERNAME
is bound to the
value "duke
" for the execution of a runnable operation. The code in the
run
method creates a StructuredTaskScope
and forks three child threads. Code
executed directly or indirectly by these threads running childTask1()
,
childTask2()
, and childTask3()
will read the value "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());
...
}
});
- 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
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 incubator release, the reference implementation provides some system properties to tune the performance of scoped values.
The system property
jdk.incubator.concurrent.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
-Djdk.incubator.concurrent.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:
- 20
-
Nested Class Summary
Modifier and TypeClassDescriptionstatic final class
A mapping of scoped values, as keys, 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> ScopedValue<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.Carrier
where
(ScopedValue<T> key, T value) Creates a newCarrier
with a single mapping of aScopedValue
key to a value.static <T> void
where
(ScopedValue<T> key, T value, Runnable op) Run an operation with aScopedValue
bound to a value in the current thread.static <T,
R> R where
(ScopedValue<T> key, T value, Callable<? extends R> op) Calls a value-returning operation with aScopedValue
bound to a value in the current thread.
-
Method Details
-
where
Creates a newCarrier
with a single mapping of aScopedValue
key to a value. TheCarrier
can be used to accumlate 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
-
where
public static <T,R> R where(ScopedValue<T> key, T value, Callable<? extends R> op) throws Exception Calls a value-returning operation with aScopedValue
bound to a value in the current thread. When the operation completes (normally or with an exception), theScopedValue
will revert to being unbound, or revert to its previous value when previously bound, in the current thread.Scoped values are intended to be used in a structured manner. If
op
creates aStructuredTaskScope
but does not close it, then exitingop
causes the underlying construct of eachStructuredTaskScope
created in the dynamic scope to be closed. This may require blocking until all child threads have completed their sub-tasks. The closing is done in the reverse order that they were created. Once closed,StructureViolationException
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
- theScopedValue
keyvalue
- the value, can benull
op
- the operation to call- Returns:
- the result
- Throws:
Exception
- if the operation completes with an exception
-
where
Run an operation with aScopedValue
bound to a value in the current thread. When the operation completes (normally or with an exception), theScopedValue
will revert to being unbound, or revert to its previous value when previously bound, in the current thread.Scoped values are intended to be used in a structured manner. If
op
creates aStructuredTaskScope
but does not close it, then exitingop
causes the underlying construct of eachStructuredTaskScope
created in the dynamic scope to be closed. This may require blocking until all child threads have completed their sub-tasks. The closing is done in the reverse order that they were created. Once closed,StructureViolationException
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
- theScopedValue
keyvalue
- the value, can benull
op
- the operation to call
-
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
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
-