Site Search:

Scoped Values and Thread-Local Alternatives

 Back>

Scoped Values and Thread-Local Alternatives

We have explored how structured concurrency makes multithreaded code safer and easier to manage. Now we look at a new primitive in the Java concurrency toolbox: ScopedValue. This feature replaces many use cases of ThreadLocal with a model better suited for virtual threads.


What Is ScopedValue?

ScopedValue is a safe, immutable, and inheritable thread context designed to replace ThreadLocal in virtual-thread environments. Unlike ThreadLocal, which relies on mutable state tied to the thread, ScopedValue is a single-assignment object bound to a well-defined scope.

You define a value in a scope, and all code within that scope — including tasks running in virtual threads — can access it.


Why Not ThreadLocal?

ThreadLocal has long been used for per-thread data like user sessions, log context, and request-scoped state. But in the virtual thread world, it poses problems:

  • It’s mutable, leading to subtle bugs in shared environments
  • It leaks memory if not cleaned up properly
  • It’s expensive to maintain across millions of virtual threads

ScopedValue solves these issues by being immutable and scope-bound.


Basic Usage

static final ScopedValue<String> USER = ScopedValue.newInstance();

void handleRequest(String userId) {
    ScopedValue.where(USER, userId).run(() -> {
        logRequest();       // prints: user=alice
        processRequest();   // can access USER.get()
    });
}

Inside the ScopedValue.where(...).run() block, the value is safely accessible. Outside, it throws IllegalStateException if you try to read it.


ScopedValue vs ThreadLocal Comparison

FeatureThreadLocalScopedValue
MutableYesNo
Garbage-safetyManual cleanup neededAuto scoped
Virtual thread friendlyNoYes
Default value supportYesNo (must be explicitly set)
InheritanceYes (but buggy)Explicit

ScopedValue and Structured Concurrency

ScopedValue works naturally with StructuredTaskScope. For example:

ScopedValue.where(CTX, "alice").run(() -> {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        scope.fork(() -> logContext());   // "alice"
        scope.fork(() -> audit());        // "alice"
        scope.join();
    }
});

All subtasks share the same value safely without mutable thread-local state.


Best Practices

  • Use ScopedValue for all new context-passing code
  • Avoid ThreadLocal in virtual thread-based systems
  • Use ScopedValue.get() only inside ScopedValue.where(...) blocks

Conclusion

ScopedValue is a modern replacement for ThreadLocal that aligns with virtual threads, structured concurrency, and clean context propagation. It brings safety and immutability to multithreaded environments and avoids common pitfalls of legacy thread-local storage.

No comments:

Post a Comment