Lukas Steinbrecher
Lead Developer und AWS Experte
Dieser Talk (hier gehts direkt zum Video, alternativ findet ihr unten eine komprimierte Zusammenfassung des Inhalts) begleitet die Super-Duper-Bank in ihrer Reise durch die komplexe Welt der asynchronen Programmierung und geht dabei auf die geläufigen Ansätze in verschiedenen Programmiersprachen ein. Wir beleuchten insbesondere, warum das neue Project Loom in Java eben nicht die derzeit in einigen Programmiersprachen populäre async/await Syntax wählt.
Im ersten Teil des Talks sprechen wir über das klassische Thread-per-Request-Modell und schauen uns an, was (Thread-) blockierende Operationen genau sind, welche Rolle dabei die Threads spielen und warum dieses Modell für Netzwerkoperationen relativ ineffizient, aber nichtsdestotrotz sich am natürlichsten anfühlt. Danach gehen wir auf die effizienteren Non-Blocking-IO- Modelle ein, sehen uns an, wie eine Event-Loop funktioniert und warum man dafür nun so Dinge wie Callbacks, Futures oder diese async/await Syntax benötigt.
Im darauffolgenden Teil sprechen wir über das Project Loom, welches eine Implementation von Green-Threads auf der Java-Plattform darstellt. Auf den ersten Blick erkennt man nicht viel Unterschied zu asynchronem Code wie z.B. in C#, außer dass man im Code keine async/await-Keywörter sieht. Wir werden sehen, dass dieses Modell grundlegend unterschiedlich ist und beispielsweise erlaubt, alten Code, der das Thread-per-Request-Modell benutzt, ohne größere bzw. im Idealfall ganz ohne Codeänderungen mit den effizienteren Non-Blocking-APIs zu verwenden. Für eine Sprache wie Java, die von Anfang an ein großes Augenmerk auf Abwärtskompatibilität gesetzt hat, ein großer Vorteil.
Wie das genau funktioniert und was es genau mit der Super-Duper-Bank auf sich hat, erfahrt ihr in dem Video vom Talk im Rahmen der Senacor StreamedCon:
Zusammenfassung Project Loom
Das Project Loom ist ein Projekt des JDK-Teams zur Implementierung von virtuellen („Green“) Threads auf der Java Plattform.
In der Java Welt ist nach wie vor das klassische Thread-per-Request Modell sehr weit verbreitet. Hierbei wird bei einem Web-Server für jeden eingehenden Request ein eigener Thread zur Abarbeitung des Requests gestartet. Dieses „Blockieren“ des Threads geschieht, um Synchronität einer Operation (z.B. Lesen eines Netzwerksockets) „vorzugaukeln“. Da Betriebsystem-Threads jedoch relativ schwer gewichtig sind, sind diese im Allgemeinen eine limitierte Ressource und oft das erste Bottleneck, welches verhindert auf einer Maschine mehr Requests pro Zeiteinheit abarbeiten zu können.
Um dieses Bottleneck zu umgehen, benutzt man nicht blockierende (Non-Blocking) APIs, welche den Thread nicht blockieren. In Node.JS oder Vertx wird auf ein Event-Loop-Ausführungsmodell gesetzt, das über Callbacks oder Futures die nun vom Konsumenten direkt zu verwaltende Asynchronität der Operationen kapselt. Die Project-Loom Entwickler wählen aber einen anderen Ansatz: Es werden nicht blockierende APIs durch existierende (synchrone) Java-APIs gekapselt und „virtual-thread-ready“ gemacht, indem die virtuellen Threads blockiert werden, bis die gewünschte Operation abgeschlossen ist. Somit wird die Synchronität des Aufrufes, so wie es auch das Betriebssystem bei blockierenden Syscalls mit „echten“ Threads macht, auf Virtual-Thread Ebene simuliert. Die Semantik des Interfaces bleibt dabei dieselbe und somit ist keine Änderung auf Konsumentenseite erforderlich.
Darstellung: Virtuelle Threads um asynchrone APIs in synchrone zu übersetzen
Wie genau ist so ein virtueller Thread nun in Project Loom implementiert? Ein virtueller Thread wird durch eine sogenannte Continuation repräsentiert. Dies stellt ein Stück sequentiellen Code dar, welche sich selber suspenden kann (z.B. wenn auf eine externe Ressource gewartet wird) und an einem späteren Zeitpunkt wieder fortgesetzt werden kann. Diese Continuations werden mittels (konfigurierbaren) Scheduler von der JVM auf echten Betriebssystem-Threads („carrier thread“) der Reihe nach ausgeführt.
Als großen Vorteil der virtuellen Threads gegenüber dem Event Loop Ansatz verspricht man sich, die moderne und effiziente Welt der Non-Blocking-APIs zu nutzen, ohne sein gewohntes Thread-per-Request Programmiermodell zu wechseln und im Idealfall ohne Code-Änderungen aus bereits existierenden Codebasen von diesen Änderungen zu profitieren. Existierende Interfaces werden nicht gebrochen und die nötigen Änderungen passieren „under the hood“.
Darstellung: JEP 353: Vorbereitung der Socket API für virtuelle Thread readiness
Auch ist es nicht notwendig, wie beispielsweise in Javascript, seinen Code in zwei Welten aufzuteilen (synchron/asynchron) (das sogenannte Blue/Red-World Problem).
Darstellung: Symptom des Blue/Red-World Problems in node.js: Selbe Funktionalität muss in beiden Varianten (synchron/asynchron) bereitgestellt werden
Preview Builds vom Project Loom gibt es unter [https://jdk.java.net/loom/]. Auf den ersten Blick wird man nicht viel Änderungen erkennen, da man versucht, bei virtuellen Threads die existierenden Thread APIs soweit möglich wiederzuverwenden.
Im Idealfall wird man als Endnutzer eines Frameworks nicht viel merken, wird jedoch nach und nach davon profitieren, wenn Unterstützung für virtuelle Threads in den Frameworks eingebaut wird. Ob man tatsächlich ohne weiteres so soviel Code ohne Änderungen wiederverwenden kann, wie von den Project-Loom Entwicklern angedacht, wird sich im Laufe der Zeit zeigen.