Java 20 Brings Scoped Values to Core-Libs
Each new version of Java brings new features for the different components of Java: specification/language, core libs, and the JVM. OpenJDK 20 is no different in this regard. In this article, we shall explore the scoped values feature in the core-libs component.
An incubating API, scoped values facilitate the sharing of data that does not change both across and within threads. They are especially suited for virtual threads.
The different components within a large Java application typically need to share data among themselves. Traditionally, thread-local variables have been used for sharing data among components. Thread-local variables are just that: local variables, typically final static fields that are created per thread. The different components may get and set their value as needed to share data.
Thread-local variables have several design issues:
- Thread-local variables are mutable; any component can change the shared state of a variable. It is therefore difficult to keep track of data flow, i.e. which component has changed the shared state of what data and in what order.
- Thread-local variables are retained unboundedly leading to memory leaks. It would be more suitable to read/write thread-local data within a bounded period and remove the data when not needed.
- Thread-local variables can lead to storage problems when sub-threads inherit and retain the data, especially when dealing with a large number of threads.
The issues discussed above get compounded when dealing with a large number of virtual threads, which are lightweight threads that share the same underlying OS thread.
Scoped values are the Java platform's method of sharing data among the different components of a Java application without using thread-local variables. A new class called jdk.incubator.concurrent.ScopedValue<T> is introduced in Java 20 to represent a scoped value as a key-value pair. Essentially, a scoped value is a value that is set once and is available for reading for a bounded period within a thread. Scoped values provide data sharing without using method arguments. The nested class jdk.incubator.concurrent.ScopedValue.Carrier is a mapping of scoped values as keys to values.
What Is the Scope of Scoped Values?
What is the scope being referred to? Scoped values are used within a dynamic scope. Static method ScopedValue.where(ScopedValue<T> key,T value,Runnable op) is used to bind a scoped value key of type ScopedValue in the first arg to the value in the second method arg. The scoped value is bound to the object only while the Runnable thread in the third arg is executing the run() method. This duration defines what is called the dynamic scope of a scoped value. After a Runnable's run() method has been completed, whether normally or with an exception, the scoped value reverts to being unbounded. Code in the dynamic scope gets a ScopedValue key’s value using the get() method. The dynamic scope of scoped values makes it possible to set different values for the same ScopedValue object in different threads. Static method where(ScopedValue<T> key, T value, Callable<? extends R> op) can be used to call a value returning operation as represented by a Callable with a ScopedValue key bound to a value in the current thread.
Rebinding of Scoped Values
A scoped value is typically declared as a final static field, for example private static final ScopedValue<String> SOME_SCOPED_VALUE = ScopedValue.newInstance();. A scoped value may be rebound to a new value with the same method where(ScopedValue<T> key,T value,Runnable op).
Inheritance is supported using a Scoped Value which can be used to share data across threads. The jdk.incubator.concurrent.StructuredTaskScope<T> class is introduced to support structured concurrency when a task splits into multiple sub-tasks. Specifically, the sub-threads can share data from the super-thread by using the StructuredTaskScope<T> class to capture the scoped value bindings that are inherited by all sub-threads started in that scope.
The main benefits or goals of scoped values are ease of use, comprehensibility, robustness, and performance.