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
| Feature | ThreadLocal | ScopedValue |
|---|---|---|
| Mutable | Yes | No |
| Garbage-safety | Manual cleanup needed | Auto scoped |
| Virtual thread friendly | No | Yes |
| Default value support | Yes | No (must be explicitly set) |
| Inheritance | Yes (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
ScopedValuefor all new context-passing code - Avoid
ThreadLocalin virtual thread-based systems - Use
ScopedValue.get()only insideScopedValue.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