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,
or CallableOp 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 Runnable's run method.
The run method, in turn, invokes a method doSomething.
private static final ScopedValue<String> NAME = ScopedValue.newInstance(); ScopedValue.where(NAME, "duke").run(() -> 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 run invokes a method that does not return a result.
The call method can be used
to invoke a method that returns a result.
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.
ScopedValue binding to a value is per-thread. Invoking run
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.where(NAME, "duke1").run(() -> doSomething());
ScopedValue.where(NAME, "duke2").run(() -> 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.where(NAME, "duchess").run(() -> 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,
scoped value bindings are captured when creating a StructuredTaskScope
and inherited by all threads started in that task scope with the
fork 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.where(NAME, "duke").run(() -> { try (var scope = StructuredTaskScope.open()) { scope.fork(() -> childTask1()); scope.fork(() -> childTask2()); scope.fork(() -> childTask3()); scope.join(); .. } });
Unless otherwise specified, passing a null argument to a method in this
class will cause a NullPointerException to be thrown.
API Note
A 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.
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 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 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 and Description |
|---|---|
| private static class | |
| public static interface | ScopedValue.
result type of the operation T, type of the exception thrown by the operation X extends Throwable>An operation that returns a result and may throw an exception. |
| public static class | ScopedValue.
A mapping of scoped values, as keys, to values. |
| pack-priv static class | ScopedValue.
An immutable map from |
| Modifier and Type | Field and Description |
|---|---|
| private final int | |
| pack-priv static IntSupplier | |
| private static final Object | |
| private static int |
| Access | Constructor and Description |
|---|---|
| private |
| Modifier and Type | Method and Description |
|---|---|
| pack-priv int | Returns: the bitmaskReturn a bit mask that may be used to determine if this ScopedValue is bound in the current context. |
| pack-priv static boolean | |
| private Object | |
| private static synchronized int | |
| public T | Returns: the value of the scoped value if bound in the current threadReturns the value of the scoped value if bound in the current thread. |
| public int | |
| public boolean | Returns: true if this scoped value is bound in the current threadReturns |
| public static < the type of the value T> ScopedValue | Returns: a newScopedValueCreates a scoped value that is initially unbound for all threads. |
| public T | Returns: the value of the scoped value if bound, otherwiseotherthe value to return if not bound other)Returns the value of this scoped value if bound in the current thread, otherwise
returns |
| public < the type of the exception that may be thrown X extends Throwable> T | Returns: the value of the scoped value if bound in the current threadthe supplying function that produces the exception to throw exceptionSupplier)Returns the value of this scoped value if bound in the current thread, otherwise throws an exception produced by the exception supplying function. |
| private static ScopedValue. | |
| private static Object[] | |
| private static void | |
| private T | |
| public static < the type of the value T> ScopedValue. | Returns: a newCarrier with a single mappingthe key, T ScopedValue keythe value, can be value)nullCreates a new |
| hash | back to summary |
|---|---|
| private final int hash | |
| hashGenerator | back to summary |
|---|---|
| pack-priv static IntSupplier hashGenerator
| |
| NEW_THREAD_BINDINGS | back to summary |
|---|---|
| private static final Object NEW_THREAD_BINDINGS | |
| nextKey | back to summary |
|---|---|
| private static int nextKey | |
| ScopedValue | back to summary |
|---|---|
| private ScopedValue() | |
| bitmask | back to summary |
|---|---|
| pack-priv int bitmask() Return a bit mask that may be used to determine if this ScopedValue is bound in the current context. Each Carrier holds a bit mask which is the OR of all the bit masks of the bound ScopedValues.
| |
| containsAll | back to summary |
|---|---|
| pack-priv static boolean containsAll(int bitmask, int targetBits) | |
| findBinding | back to summary |
|---|---|
| private Object findBinding() Return the value of the scoped value or NIL if not bound. Consult the cache, and only if the value is not found there search the list of bindings. Update the cache if the binding was found. | |
| generateKey | back to summary |
|---|---|
| private static synchronized int generateKey() | |
| get | back to summary |
|---|---|
| public T get() Returns the value of the scoped value if bound in the current thread.
| |
| hashCode | back to summary |
|---|---|
| public int hashCode() Overrides java. Doc from java. Returns a hash code value for this object. This method is
supported for the benefit of hash tables such as those provided by
The general contract of
| |
| isBound | back to summary |
|---|---|
| public boolean isBound() Returns
| |
| newInstance | back to summary |
|---|---|
| public static <T> ScopedValue Creates a scoped value that is initially unbound for all threads.
| |
| orElse | back to summary |
|---|---|
| public T orElse(T other) Returns the value of this scoped value if bound in the current thread, otherwise
returns
| |
| orElseThrow | back to summary |
|---|---|
| public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws Supplier-:X Returns the value of this scoped value if bound in the current thread, otherwise throws an exception produced by the exception supplying function.
| |
| scopedValueBindings | back to summary |
|---|---|
| private static ScopedValue. | |
| scopedValueCache | back to summary |
|---|---|
| private static Object[] scopedValueCache() | |
| setScopedValueCache | back to summary |
|---|---|
| private static void setScopedValueCache(Object[] cache) | |
| slowGet | back to summary |
|---|---|
private T slowGet()
| |
| where | back to summary |
|---|---|
| public static <T> ScopedValue. Creates a new ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
| |
| Modifier and Type | Class and Description |
|---|---|
| private static class |
| Modifier and Type | Field and Description |
|---|---|
| pack-priv static final int | |
| pack-priv static final int | |
| pack-priv static final int | |
| pack-priv static final int |
| Access | Constructor and Description |
|---|---|
| private |
| Modifier and Type | Method and Description |
|---|---|
| private static boolean | |
| private static Object | |
| pack-priv static void | |
| pack-priv static int | |
| private static int | |
| pack-priv static int | |
| pack-priv static void | |
| pack-priv static int | |
| private static int | |
| pack-priv static int | |
| private static void | |
| private static void | |
| private static void |
| INDEX_BITS | back to summary |
|---|---|
| pack-priv static final int INDEX_BITS | |
| PRIMARY_MASK | back to summary |
|---|---|
| pack-priv static final int PRIMARY_MASK | |
| TABLE_MASK | back to summary |
|---|---|
| pack-priv static final int TABLE_MASK | |
| TABLE_SIZE | back to summary |
|---|---|
| pack-priv static final int TABLE_SIZE | |
| Cache | back to summary |
|---|---|
| private Cache() | |
| chooseVictim | back to summary |
|---|---|
| private static boolean chooseVictim() | |
| getKey | back to summary |
|---|---|
| private static Object getKey(Object[] objs, int n) | |
| invalidate | back to summary |
|---|---|
| pack-priv static void invalidate(int toClearBits) | |
| primaryIndex | back to summary |
|---|---|
| pack-priv static int primaryIndex(int hash) | |
| primarySlot | back to summary |
|---|---|
| private static int primarySlot(ScopedValue<?> key) | |
| primarySlot | back to summary |
|---|---|
| pack-priv static int primarySlot(int hash) | |
| put | back to summary |
|---|---|
| pack-priv static void put(ScopedValue<?> key, Object value) | |
| secondaryIndex | back to summary |
|---|---|
| pack-priv static int secondaryIndex(int hash) | |
| secondarySlot | back to summary |
|---|---|
| private static int secondarySlot(ScopedValue<?> key) | |
| secondarySlot | back to summary |
|---|---|
| pack-priv static int secondarySlot(int hash) | |
| setKey | back to summary |
|---|---|
| private static void setKey(Object[] objs, int n, Object key) | |
| setKeyAndObjectAt | back to summary |
|---|---|
| private static void setKeyAndObjectAt(int n, Object key, Object value) | |
| setKeyAndObjectAt | back to summary |
|---|---|
| private static void setKeyAndObjectAt(Object[] cache, int n, Object key, Object value) | |
| Modifier and Type | Field and Description |
|---|---|
| private static final int | |
| private static final int | |
| private static final int | |
| private static final JavaUtilConcurrentTLRAccess |
| Access | Constructor and Description |
|---|---|
| private |
| CACHE_TABLE_SIZE | back to summary |
|---|---|
| private static final int CACHE_TABLE_SIZE | |
| MAX_CACHE_SIZE | back to summary |
|---|---|
| private static final int MAX_CACHE_SIZE | |
| SLOT_MASK | back to summary |
|---|---|
| private static final int SLOT_MASK | |
| THREAD_LOCAL_RANDOM_ACCESS | back to summary |
|---|---|
| private static final JavaUtilConcurrentTLRAccess THREAD_LOCAL_RANDOM_ACCESS | |
| Constants | back to summary |
|---|---|
| private Constants() | |
| Modifier and Type | Method and Description |
|---|---|
| public T |
| call | back to summary |
|---|---|
| public T call() throws CallableOp:X Executes this operation.
| |
A Carrier is used to accumulate mappings so that an operation (a Runnable or CallableOp) 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.
A Carrier is immutable and thread-safe. The where method returns a new Carrier object,
it does not mutate an existing mapping.
Unless otherwise specified, passing a null argument to a method in
this class will cause a NullPointerException to be thrown.
| Modifier and Type | Field and Description |
|---|---|
| pack-priv final int | |
| pack-priv final ScopedValue | |
| pack-priv final ScopedValue. | |
| pack-priv final Object |
| Access | Constructor and Description |
|---|---|
| pack-priv |
| Modifier and Type | Method and Description |
|---|---|
| public < the type of the result of the operation R, type of the exception thrown by the operation X extends Throwable> R | Returns: the resultthe operation to run opCalls a value-returning operation with each scoped value in this mapping bound to its value in the current thread. |
| pack-priv Object | |
| public < the type of the value T> T | Returns: the valuethe key)ScopedValue keyReturns the value of a |
| pack-priv ScopedValue | |
| pack-priv static <T> ScopedValue. | |
| public void | |
| private <R, X extends Throwable> R | runWith(ScopedValue.
Execute the action with a set of ScopedValue bindings. |
| private void | runWith(ScopedValue.
Execute the action with a set of |
| private static <T> ScopedValue. | where(ScopedValue<T> key, T value, ScopedValue.
Add a binding to this map, returning a new Carrier instance. |
| public < the type of the value T> ScopedValue. | Returns: a newCarrier with the mappings from this carrier plus the new mappingthe key, T ScopedValue keythe value, can be value)nullReturns a new |
| bitmask | back to summary |
|---|---|
| pack-priv final int bitmask | |
| key | back to summary |
|---|---|
| pack-priv final ScopedValue<?> key | |
| prev | back to summary |
|---|---|
| pack-priv final ScopedValue. | |
| value | back to summary |
|---|---|
| pack-priv final Object value | |
| Carrier | back to summary |
|---|---|
| pack-priv Carrier(ScopedValue<?> key, Object value, ScopedValue. | |
| call | back to summary |
|---|---|
| public <R, X extends Throwable> R call(ScopedValue. Calls a value-returning operation with each scoped value in this mapping bound
to its value in the current thread.
When the operation completes (normally or with an exception), each scoped value
in the mapping will revert to being unbound, or revert to its previous value
when previously bound, in the current thread. If Scoped values are intended to be used in a structured manner. If code
invoked directly or indirectly by the operation creates a
| |
| get | back to summary |
|---|---|
| pack-priv Object get() | |
| get | back to summary |
|---|---|
| public <T> T get(ScopedValue<T> key) Returns the value of a
| |
| getKey | back to summary |
|---|---|
| pack-priv ScopedValue | |
| of | back to summary |
|---|---|
| pack-priv static <T> ScopedValue. | |
| run | back to summary |
|---|---|
| public void run(Runnable op) Runs an operation with each scoped value in this mapping bound to its value
in the current thread.
When the operation completes (normally or with an exception), each scoped value
in the mapping will revert to being unbound, or revert to its previous value
when previously bound, in the current thread. If Scoped values are intended to be used in a structured manner. If code
invoked directly or indirectly by the operation creates a
| |
| runWith | back to summary |
|---|---|
| private <R, X extends Throwable> R runWith(ScopedValue. Execute the action with a set of ScopedValue bindings. The VM recognizes this method as special, so any changes to the name or signature require corresponding changes in JVM_FindScopedValueBindings().
| |
| runWith | back to summary |
|---|---|
| private void runWith(ScopedValue. Execute the action with a set of
| |
| where | back to summary |
|---|---|
| private static <T> ScopedValue. Add a binding to this map, returning a new Carrier instance. | |
| where | back to summary |
|---|---|
| public <T> ScopedValue. Returns a new
| |
ScopedValue to values.
Unless otherwise specified, passing a null argument to a constructor
or method in this class will cause a NullPointerException to be thrown.
| Modifier and Type | Field and Description |
|---|---|
| pack-priv final ScopedValue. | |
| pack-priv final int | |
| pack-priv static final ScopedValue. | |
| private static final Object | |
| pack-priv final ScopedValue. |
| Access | Constructor and Description |
|---|---|
| pack-priv | |
| protected |
| Modifier and Type | Method and Description |
|---|---|
| pack-priv Object |
| bindings | back to summary |
|---|---|
| pack-priv final ScopedValue. | |
| bitmask | back to summary |
|---|---|
| pack-priv final int bitmask | |
| EMPTY_SNAPSHOT | back to summary |
|---|---|
| pack-priv static final ScopedValue. | |
| NIL | back to summary |
|---|---|
| private static final Object NIL | |
| prev | back to summary |
|---|---|
| pack-priv final ScopedValue. | |
| Snapshot | back to summary |
|---|---|
| pack-priv Snapshot(ScopedValue. | |
| Snapshot | back to summary |
|---|---|
| protected Snapshot() | |
| find | back to summary |
|---|---|
| pack-priv Object find(ScopedValue<?> key) | |