BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News JEP 481: Third Preview of Scoped Values API Brings Key Enhancements in JDK 23

JEP 481: Third Preview of Scoped Values API Brings Key Enhancements in JDK 23

JEP 481, Scoped Values (Third Preview), formerly known as Extent-Local Variables (Incubator), has been completed for JDK 23. This JEP offers a third preview with one change to gain additional experience and feedback from one round of incubation and two rounds of preview, namely: JEP 464, Scoped Values (Second Preview), delivered in JDK 22; JEP 446, Scoped Values (Preview), delivered in JDK 21; and JEP 429, Scoped Values (Incubator), delivered in JDK 20. This feature enables the sharing of immutable data within and across threads. This is preferred to thread-local variables, especially when using large numbers of virtual threads.

The re-preview of the Scoped Values API in JDK 23 introduces a new change to the ScopedValue.callWhere method. The operation parameter of this method is now a functional interface. It allows the Java compiler to infer whether a checked exception might be thrown. Consequently, the ScopedValue.getWhere method is no longer needed and has been removed. This leads to cleaner code and improved performance in frequent data-sharing scenarios.

Scoped values enable a method to share immutable data with its callees and child threads, making it easier to manage and reason about data flow compared to thread-local variables. They offer lower space and time costs, especially when combined with virtual threads (JEP 444) and structured concurrency (JEP 480).

However, thread-local variables, introduced in Java 1.2, have been the traditional method for facilitating data sharing across methods within the same thread. Despite their long-standing use, they come with several drawbacks. One major issue is their unconstrained mutability, allowing any code to change the thread-local variable's value at any time, which can lead to potential inconsistencies. Another drawback is their unbounded lifetime; values can persist longer than necessary, often resulting in memory leaks if developers forget to call the remove method. Additionally, inheriting thread-local variables across threads adds significant overhead, as each child thread must allocate storage for every thread-local variable previously written in the parent thread, negatively impacting performance.

Scoped values address these issues by ensuring data is immutable and only accessible within a defined scope, thereby enhancing both security and performance.

To illustrate the benefits of scoped values, consider a web framework where context needs to be shared across different methods without explicitly passing it as a parameter. Using thread-local variables, the framework might look like this:

class Framework {

  private final static ThreadLocal<FrameworkContext> CONTEXT = new ThreadLocal<>();

  void serve(Request request, Response response) {
    var context = createContext(request);
    CONTEXT.set(context);
    Application.handle(request, response);
  }

  public PersistedObject readKey(String key) {
    var context = CONTEXT.get();
    var db = getDBConnection(context);
    return db.readKey(key);
  }
}

With scoped values, the implementation becomes cleaner and more efficient:

class Framework {

  private final static ScopedValue<FrameworkContext> CONTEXT = ScopedValue.newInstance();

  void serve(Request request, Response response) {
    var context = createContext(request);
    ScopedValue.runWhere(CONTEXT, context, () -> Application.handle(request, response));
  }

  public PersistedObject readKey(String key) {
    var context = CONTEXT.get();
    var db = getDBConnection(context);
    return db.readKey(key);
  }
}

Using a thread-local variable avoids the need to pass a FrameworkContext as a method argument when the framework calls user code and when user code calls a framework method back. The thread-local variable serves as a hidden method parameter: a thread that calls CONTEXT.set in Framework.serve and then CONTEXT.get in Framework.readKey will automatically see its own local copy of the CONTEXT variable. In effect, the ThreadLocal field serves as a key that is used to look up a FrameworkContext value for the current thread. On the other hand, using scoped values simplifies this process by eliminating the need for a mutable state and ensuring that the context is only accessible within the defined scope of the runWhere method, providing a more robust and performance-friendly approach.

In conclusion, the Scoped Values API significantly enhances the way data is shared across methods and threads in Java, promoting better coding practices and improving application performance. It aligns well with modern concurrency models, particularly with the advent of virtual threads, making it a critical addition for developers working on highly concurrent applications.

About the Author

Rate this Article

Adoption
Style

BT