Loslegen
Javaneer
Zurück zur Stufe
Stufe 7·Advanced Concurrency & Modern Java

Structured Concurrency & Scoped Values

Java 21-25's structured concurrency treats a group of concurrent tasks as one unit of work, with scoped values as a safe replacement for thread-locals.

15 Min. LesezeitExperte

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

Spawning concurrent tasks with an executor works, but it's unstructured: if you start three subtasks and one fails, the others keep running, error handling is manual, and cancellation is fiddly. Structured concurrency (finalized around Java 25, after previews from 21) brings the discipline of structured programming to threads: a group of concurrent subtasks becomes one unit of work with a clear scope.

Concurrency with a scope

The idea: if you split a task into concurrent subtasks, they should all complete (or all be cancelled) before the parent continues - just as a method's local variables live and die within its braces. StructuredTaskScope enforces this:

// run two subtasks concurrently as one unit of work (Java 21-25 API)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var user = scope.fork(() -> fetchUser(id));       // subtask 1
    var orders = scope.fork(() -> fetchOrders(id));   // subtask 2

    scope.join();                 // wait for both
    scope.throwIfFailed();        // if either failed, propagate and cancel the other

    return new Summary(user.get(), orders.get());   // both succeeded
}   // scope closes: any still-running subtask is guaranteed cancelled

The try-with-resources block is the lifetime of the concurrent work. You can't leak a subtask past the block, and ShutdownOnFailure gives you the sensible default: if one subtask fails, the others are cancelled immediately rather than wasting effort. This is the opposite of the old executor model, where a failed subtask left its siblings running blindly.

Why "structured" matters

Structured concurrency makes concurrent code reason like sequential code: clear scope, automatic propagation of errors and cancellation, and no orphaned threads. Combined with virtual threads (each fork runs on one), you get readable, correct, highly concurrent code without the bookkeeping executors demand.

Scoped values: better than thread-locals

A companion feature, scoped values, replaces the error-prone ThreadLocal for passing context (a user id, a request id) down a call chain. A scoped value is immutable and bound only for a bounded dynamic scope, which is safe and efficient with millions of virtual threads (where thread-locals would be costly and leak-prone):

private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();

ScopedValue.where(CURRENT_USER, user).run(() -> handleRequest());
// inside handleRequest and anything it calls: CURRENT_USER.get() returns user, immutably

The parallel with structured programming

Decades ago, goto gave way to structured control flow (if/loops/functions) with clear entry and exit. Structured concurrency is the same leap for threads: a scope has one entry and one exit, subtasks can't outlive it, and errors and cancellation flow along the structure. It turns 'fire off threads and hope' into concurrency you can actually reason about.

A project manager who won't leave until the team is done

The old executor model is like handing out three tasks to contractors and walking away - if one quits, the others keep working on a project that's already doomed, and nobody's tracking them. Structured concurrency is a project manager who splits the work, stays until every subtask finishes, and the moment one fails, calls off the rest so no effort is wasted - then reports a single result. The team's work is bracketed by the manager's presence: nothing outlives the meeting, and one clear outcome comes out. That bracketing is the 'structure.'

Two calls, fail fast

You fork two concurrent subtasks and only want a result if both succeed - if either fails, you want to stop the other immediately and report the error. Explain how structured concurrency handles this compared to submitting two tasks to a plain ExecutorService.

What problem does structured concurrency solve compared to submitting tasks to a plain executor?

Key takeaways

  • Structured concurrency (Java 21-25) treats a group of concurrent subtasks as one unit of work with a clear scope.
  • A StructuredTaskScope ties subtask lifetimes to a try-with-resources block: none can outlive it, and ShutdownOnFailure cancels siblings when one fails.
  • It makes concurrent code reason like sequential code - automatic error propagation and cancellation, no orphaned threads.
  • Each fork typically runs on a virtual thread, combining readability with high concurrency.
  • Scoped values replace ThreadLocal for passing immutable context down a call chain - safe and efficient with millions of virtual threads.
War diese Lektion hilfreich?
Diese Seite auf GitHub bearbeiten