Java 20 erweitert die Nebenläufigkeit mit Scoped Values

Die Neuerungen des aktuellen Java-Release zielen vor allem auf nebenläufige Programmierung und die Anbindung an andere Programmiersprachen.

In Pocket speichern vorlesen Druckansicht 26 Kommentare lesen

(Bild: Shutterstock)

Lesezeit: 7 Min.
Von
  • Rainald Menge-Sonnentag
Inhaltsverzeichnis

Oracle hat im planmäßigen Sechsmonatstakt Java 20 freigegeben. Das Release bringt insgesamt sieben Neuerungen in Java Enhancement Proposals (JEPs) mit, von denen drei aus Project Loom und je zwei aus den Projekten Amber und Panama stammen. Sechs der JEPs sind Weiterentwicklungen aus vorherigen JDKs. Komplett neu sind lediglich die Scoped Values.

Bei der Übersicht stapelt Oracle ausnahmsweise tief: JDK 20 bringt nicht sechs, sondern sieben JEPs.

(Bild: Oracle)

Project Loom zielt auf eine verbesserte und schlankere Nebenläufigkeit für Java-Programme. Loom bedeutet Webstuhl, also das Werkzeug, um die Fäden (Threads) zu einem großen Ganzen zusammenzufügen. Java 20 webt einen neuen Faden im Inkubator ein und erweitert zwei JEPs aus Java 19.

JEP 436 Virtual Threads ist in Java 19 als JEP 425 gestartet. Es bezeichnet ein neues Konzept für nebenläufige Anwendungen. Bisher hat Java in Multithreading-Anwendungen für jeden Thread im Code einen Thread im Betriebssystem genutzt. Die virtuellen Threads schonen die Ressourcen, indem das JDK eine Zwischenebene einführt, die sich um das Verteilen der Threads im Code auf die im Betriebssystem kümmert.

In Java gab es bereits in Version 1.1 das Konzept der Green Threads, bei dem das JDK die Verteilung übernahm. Allerdings legte es alle Code-Threads auf lediglich einen im Betriebssystem. Das neue Konzept ist dagegen darauf ausgelegt, für eine große Zahl virtueller Threads eine begrenzte Zahl an Betriebssystem-Threads zu verwenden. Die Runtime kümmert sich zudem darum, virtuelle Threads zu pausieren, die eine blockende Operation aufrufen. Das Proposal befinden sich in der zweiten Preview-Phase.

Das JEP 429 Scoped Values führt ein neues Konzept ein, um unveränderliche Werte innerhalb von und zwischen Threads auszutauschen. Es ist effizienter als der Einsatz von ThreadLocal-Variablen. Wie diese gelten die Werte prinzipiell nur für den jeweiligen Thread. Der gleiche ScopedValue kann in unterschiedlichen Threads verschiedene Werte annehmen. Typischerweise sollte man Scoped Values als public static umsetzen. Scope ist anders zu verstehen als sonst in Java: Es handelt sich nicht um den lexikalischen Bereich einer Klasse oder Funktion, sondern um einen dynamischen Bereich, den die Laufzeit durch das Binden des Werts vorgibt.

Im Vergleich zu den ThreadLocal-Variablen haben die Scoped Values einige Vorteile. Zum einen sind sie grundsätzlich unveränderlich (immutable) und neue Inhalte lassen sich nur über ein Rebinding zuweisen, während ThreadLocal-Variablen über set den Wert ändern können. Damit lässt sich schwer nachvollziehen, welche Codestellen zu welcher Zeit die Werte ändern können.

Außerdem haben die Scoped Values eine fest vorgegebene Lebenszeit: Für den Aufruf

ScopedValue.where(V, <value>)
           .run(() -> { ... V.get() ...  });

erstellt die Laufzeitumgebung einen spezifischen Wert und bindet ihn an den Kontext in run. Der darin aufgerufene Code hat Zugriff auf den Wert, und sobald die Methode run beendet ist, gibt die Laufzeit das Binding frei, womit der Garbage Collector den Wert aufräumen darf. Ein manuelles ThradLocal.remove() ist damit überflüssig – und führt nicht zu unnötig genutztem Speicher, wenn jemand das manuelle Entfernen vergisst. Außerdem können Kind-Threads automatisch auf die Scoped Values der Eltern zugreifen und belegen damit keinen zusätzlichen Speicher.

Das JEP 437 Structured Concurrency setzt das in Java 19 mit derselben Beschreibung als JEP 428 eingeführte Proposal fort. Es befindet sich wie die neuen Scoped Values im Inkubator. Das heißt, dass im Gegensatz zu den Preview-Features die API noch nicht vollständig spezifiziert ist. Mit dem Konzept der strukturierten Nebenläufigkeit, das andere Sprachen und Frameworks ebenfalls bieten, lassen sich Tasks aus unterschiedlichen Threads in einer Einheit verwalten, um die Wartbarkeit und Zuverlässigkeit von nebenläufigem Code zu verbessern.

Mit der Klasse StructuredTaskScope kann man einen Task als Gruppe nebenläufiger Subtasks anlegen und als geschlossene Einheit verwalten. Die API kennt unter anderem die Vorgabe ShutdownOnFailure, die dafür sorgt, dass beim Auftreten eines Fehlers in einem Subtask die anderen Tasks entsprechend gestoppt werden. Analog dazu gibt ShutdownOnSuccess vor, dass alle Subtasks ihre Arbeit beenden, wenn einer erfolgreich durchgelaufen ist.

Die zwei Proposals für Project Amber knüpfen nahtlos an die vorherige Java-Version an. In dem "Bernstein"-Projekt finden sich kleinere Sprachfeatures, die vor allem die Produktivität steigern sollen. JEP 432 Record Patterns ist die zweite Preview nach dem JEP 405 im JDK 19. Es dient dazu, Werte in Records zu dekonstruieren, wobei die Patterns verschachtelt sein dürfen.

JEP 433 Pattern Matching for switch ist bereits die vierte Preview und damit der dritte Nachfolger des in Java 17 unter 406 eingeführten Proposal. Für Java 21 steht mit JEP 441 die endgültige Integration an. Der Name ist mehr oder weniger selbsterklärend: Das Konstrukt vergleicht einen Ausdruck anhand eines switch-Blocks mit Mustern, um beispielsweise folgenden Code aus dem JEP

static String formatter(Object obj) {
    String formatted = "unknown";
    if (obj instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (obj instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (obj instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (obj instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

durch das deutlich besser lesbare

static String formatterPatternSwitch(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}
}

zu ersetzen.

Auch die beiden Proposals für Project Panama gehen im Vergleich zu Java 19 jeweils nur einen Schritt vorwärts. Das Projekt zielt auf die Anbindung von Java-Programmen an Nicht-Java- beziehungsweise JVM-Komponenten wie C-basierte Libraries und Interfaces.

JEP 434 Foreign Function & Memory API ist die zweite Preview nach JEP 424 im JDK 19. Das Proposal soll eine einheitliche Schnittstelle zu Code und Daten jenseits der Java-Runtime bieten und das Java Native Interface (JNI) ersetzen. Die unter dem Akronym FFM geführte API kombiniert JEP 389 Foreign Linker API aus Java 16 und die in Java 14 als JEP 370 gestartete Foreign-Memory Access API.

Die letzte größere Neuerung in Java 20 JEP 438 Vector API befindet sich bereits in der fünften Inkubator-Runde, ist also seit Java 16 an Bord. Die API soll eine Schnittstelle für Vektorrechnung auf CPU-Ebene bringen.

Daneben bringt Java 20 wie üblich einige Bugfixes. Mehr zu den Neuerungen findet sich im Java-Blog bei Oracle. Im Herbst steht mit JDK 21 ein größerer Schritt für Java bevor: das nächste Release mit Long-term Support.

Die OpenJDK-Variante lässt sich von der JDK-Site herunterladen. Für die kommerzielle Variante hat Oracle Ende Januar das Lizenzmodell angepasst: Der Preis für die neue Oracle Java SE Universal Subscription richtet sich nicht mehr nach der tatsächlichen Java-User ("Named User Plus"-Lizenz) oder Prozessorzahl ("Processor"-Lizenz), sondern nach der Mitarbeiterzahl des Unternehmens, das Partner wie Vertreter, Beraterinnen und Outsourcer potenziell mitzählen muss. Oracle preist das Modell als einfacher an, für die meisten Unternehmen dürfte es aber vor allem teurer werden.

(rme)