Du betrachtest gerade Java Threading Teil 3: Ausblick, Structured Concurrency und was noch kommt
Header created with ChatGPT, GIMP

Java Threading Teil 3: Ausblick, Structured Concurrency und was noch kommt

In Teil 2 dieser Reihe habe ich bewusst einen Satz nur angerissen: Dass Structured Concurrency in Java 25 immer noch als Preview-Feature läuft, mittlerweile in der fünften Runde. Als ich mir dafür die Historie angeschaut habe, ist mir erst aufgefallen, wie lange dieses eine Feature schon unterwegs ist. Grund genug, ihm einen eigenen, dritten Teil zu widmen.

Warum virtuelle Threads allein nicht reichen

Virtuelle Threads (Teil 2 dieser Reihe) lösen das Kostenproblem von Threads, aber nicht das Problem, dass nebenläufiger Code trotzdem leicht aus dem Ruder laufen kann. Ein klassisches Beispiel: Ihr startet zwei nebenläufige Teilaufgaben (etwa zwei API-Aufrufe), die eine schlägt fehl, die andere läuft aber einfach weiter. Im Hintergrund. Unbeaufsichtigt, ohne dass irgendjemand sie noch abbricht oder auf sie wartet. Mit reinem ExecutorService und Future ist genau das ziemlich leicht möglich, und es entstehen sogenannte „verwaiste“ Tasks (orphaned tasks) oder Thread-Leaks. Bei meinen OCR-Batches wäre das ziemlich ärgerlich. Scheitert einer von mehreren parallel laufenden Verarbeitungsschritten für einen Batch, will ich nicht, dass die restlichen Schritte einfach unbeaufsichtigt weiterlaufen.

Genau da setzt Structured Concurrency an. Die Grundidee ist, dass nebenläufige Teilaufgaben immer eine klare, sichtbare Eltern-Kind-Beziehung zu dem Code haben, der sie gestartet hat. Vergleichbar mit einem Try-Block, der garantiert, dass alle seine Ressourcen wieder aufgeräumt werden, bevor er verlassen wird.

Structured Concurrency: Die Grundidee

Der zentrale Baustein heißt StructuredTaskScope. Man öffnet einen Scope, „forkt“ innerhalb davon eine oder mehrere Teilaufgaben und wartet gemeinsam auf deren Ergebnis. Verlässt man den Scope (das try-Block-Ende), ist garantiert: Entweder sind alle Teilaufgaben fertig, oder sie wurden abgebrochen. Nichts läuft unbeaufsichtigt weiter.

So sieht das mit der aktuellen API (Stand Java 25) aus:

List<Aktienkurs> ladeAktienkurse(List<String> symbole) throws InterruptedException {
    try (var scope = StructuredTaskScope.open(Joiner.<Aktienkurs>allSuccessfulOrThrow())) {
        symbole.forEach(symbol -> scope.fork(() -> aktienApi.holeKurs(symbol)));

        return scope.join()
            .map(StructuredTaskScope.Subtask::get)
            .toList();
    } // scope.join() garantiert: alle Teilaufgaben fertig oder abgebrochen
}

Joiner.allSuccessfulOrThrow() sorgt hier dafür, dass, sobald eine der Teilaufgaben scheitert, automatisch alle anderen noch laufenden Teilaufgaben abgebrochen werden und der Fehler sauber nach oben propagiert, statt dass man mit halb fertigen Ergebnissen und ein paar Zombie-Threads dasteht. Genau das Muster, das ich mir für meine Batch-Verarbeitung wünsche.

Eine ungewöhnlich lange Preview-Reise

Alan Bateman und Ron Pressler beide bei Oracle, sind hier maßgeblich daran beteiligt. Beide sind am Projekt Loom stark involviert und bringen unter Beteiligung der Community das Thema voran. Beim Recherchicheren hatte ich eine Überraschuing. Obwohl ich langjährig mit Java Structured Concurrency ist eines der am längsten in der Preview-Phase verweilenden Features der letzten Jahre.

  • Java 19 (JEP 428) und Java 20 (JEP 437): als Incubator-API, also noch nicht mal offizielle Preview
  • Java 21 (JEP 453): erste richtige Preview, hier hieß die Kern-Methode noch fork() mit Rückgabe eines Future
  • Java 22 (JEP 462) und Java 23 (JEP 480): Zweite und dritte Preview-Runde, jeweils mit API-Anpassungen
  • Java 24 (JEP 499): vierte Preview
  • Java 25 (JEP 505): fünfte Preview, mit einer größeren Überarbeitung: Die öffentlichen Konstruktoren von StructuredTaskScope sind durch statische Fabrikmethoden (StructuredTaskScope.open(...)) ersetzt worden, und das Joiner-Interface aus dem Beispiel oben kam neu dazu
  • Java 26 (JEP 525): sechste Preview, unter anderem mit einer onTimeout()-Methode für Joiner
  • Java 27 (JEP 533, vorgeschlagen): siebte Preview, mit einem zusätzlichen Typ-Parameter für die Art der Exception, die join() werfen kann

Sieben Anläufe (Stand jetzt (07-07-2026) für ein einziges Feature, das selbst für JDK-Verhältnisse ungewöhnlich viel ist. Es zeigt aber auch, wie ernst das Feedback aus der Community hier genommen wird. Die API hat sich zwischen Java 21 und 25 spürbar verändert, nicht nur kosmetisch.

Scoped Values im Zusammenspiel

Scoped Values (seit Java 25 final, siehe Teil 2) sind kein Zufall. Zur gleichen Zeit gereift wie Structured Concurrency, sind beide eng miteinander verzahnt gedacht. Forkt ihr innerhalb eines StructuredTaskScope eine Teilaufgabe, erbt diese automatisch die ScopedValue-Bindungen des Elternscopes. Ein Wert, der zum Beispiel eine Request-ID oder einen Sicherheitskontext trägt, muss also nicht manuell an jede Teilaufgabe durchgereicht werden. Sondern ist einfach da. Sauber begrenzt auf den jeweiligen Scope und ohne die Fallstricke von ThreadLocal (unbegrenzte Lebensdauer, Veränderlichkeit, Vererbungsprobleme bei Thread-Pools).

Wohin geht die Reise noch?

Eine verlässliche Prognose, wann Structured Concurrency final wird, würde ich mir nicht zutrauen. Bei sieben Preview-Runden und weiterhin spürbaren API-Änderungen (siehe JEP 533,) wirkt es nicht so, als stünde das unmittelbar bevor. Was sich aber klar abzeichnet:

  • Virtuelle Threads, Scoped Values und Structured Concurrency wachsen zu einem gemeinsamen, aufeinander abgestimmten Werkzeugkasten zusammen. Keins der drei Features ergibt für sich allein das volle Bild.
  • Das alte java.util.concurrent-Werkzeug (Executors, Fork/Join, CompletableFuture) aus Teil 1 dieser Reihe verschwindet nicht. Es wird aber zunehmend zur „unteren Ebene“, auf der die neueren, strukturierten APIs aufbauen.
  • Wer produktiv unterwegs ist, sollte Structured Concurrency vorerst als das behandeln, was es ist. Ein spannendes, aber noch nicht stabiles Preview-Feature, bei dem sich der Code bis zur finalen Version noch anpassen lässt. Für meine OCR-Batches heißt das konkret: Ich baue vorerst mit virtuellen Threads und einem klassischen ExecutorService, behalte StructuredTaskScope aber im Blick.

Fazit der Reihe

Von Green Threads (Teil 1) über die Stabilisierung virtueller Threads (Teil 2) bis zu Structured Concurrency, das immer noch an seiner finalen Form feilt (dieser Teil). Java-Nebenläufigkeit hat in den letzten Jahren einen deutlich größeren Sprung gemacht als in den zwei Jahrzehnten davor. Der alte Entwurf, mit dem diese Reihe ursprünglich mal angefangen hat, ist am Ende, ausgelöst durch ganz praktischen Bedarf beim Bau meiner OCR-Software, zu einem ziemlich kompletten Bild dessen geworden, wo Java bei diesem Thema herkommt, steht und hinwill.

Was meint ihr: Lohnt es sich schon, Structured Concurrency im Preview-Modus auszuprobieren, oder wartet ihr lieber die finale Version ab?

Schreibe einen Kommentar