Nebenläufigkeit in Java: Von klassischen Threads zu Goroutinen und async/await
\n\nJava ist eine Sprache, die von Geburt an mit mehreren Aufgaben gleichzeitig umgehen kann. Aber die Welt steht nicht still. Heute sprechen wir über Nebenläufigkeit in Java – von den guten alten Threads (Thread) über virtuelle Threads (Goroutinen aus Project Loom) bis hin zum asynchronen Ansatz mit async/await (am Beispiel von CompletableFuture).
\n\nWenn Sie in Java programmieren und möchten, dass Ihr Server unter Last nicht „lahmt“ und der Code lesbar bleibt, ist dieser Artikel genau das Richtige für Sie. Los geht's!
\n\nWarum ist Nebenläufigkeit für moderne Java-Entwickler wichtig?
\n\nStellen Sie sich vor, Ihre Website verarbeitet 10.000 Anfragen pro Sekunde. Wenn jede Anfrage sequenziell ausgeführt wird, warten die Benutzer eine Ewigkeit. Nebenläufigkeit in Java ermöglicht es, viele Aufgaben „gleichzeitig“ auszuführen und die Prozessorressourcen effizient zu nutzen.
\n\nModerne Anwendungen sind nicht nur CRUD. Es sind Microservices, Datenstromverarbeitung, WebSocket, Echtzeit-Analytik. Ohne Verständnis von Multithreading in Java riskieren Sie, Code zu schreiben, der entweder mit einem OutOfMemoryError abstürzt oder sich wie eine Schnecke bewegt.
\n\nStufe 1: Klassische Threads (Thread) – das Fundament
\n\nEin Thread in Java ist die niedrigste Abstraktionsebene, um Code parallel auszuführen. Jeder Thread ist ein separater „leichter“ Prozess innerhalb des Betriebssystems.
\n\nSo sieht ein einfaches Beispiel zum Erstellen eines Threads aus:
\n\npublic class ThreadExample {\n public static void main(String[] args) {\n Thread thread = new Thread(() -> {\n for (int i = 0; i < 5; i++) {\n System.out.println("Hallo aus dem Thread: " + i);\n try {\n Thread.sleep(1000);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n }\n });\n thread.start();\n System.out.println("Der Hauptthread arbeitet weiter");\n }\n}\n\nVorteile:
\n- \n
- Einfachheit der Erstellung – new Thread(() -> { ... }).start(); \n
- Direkte Steuerung des Lebenszyklus. \n
Nachteile:
\n- \n
- Das Erstellen eines Threads ist eine teure Operation (Overhead durch Stack, Kontextwechsel). \n
- Begrenzung der Anzahl: 10.000 Threads können Ihre JVM lahmlegen. \n
- Komplexität der Synchronisation: Deadlock, Race Condition. \n
Der klassische Ansatz ist gut für kleine Aufgaben, aber für High-Load-Anwendungen ungeeignet. Genau hier kommen virtuelle Threads in Java ins Spiel.
\n\nStufe 2: Virtuelle Threads (Project Loom) – Goroutinen in Java
\n\nWenn Sie schon von Goroutinen in Go gehört haben, dann sind virtuelle Threads in Java fast dasselbe. Sie sind in Java 21 (Project Loom) erschienen und verändern die Spielregeln komplett.
\n\nVirtuelle Threads sind „leichtgewichtige“ Threads, die von der JVM und nicht vom Betriebssystem verwaltet werden. Man kann sie millionenfach erstellen, und sie verbrauchen nur minimalen Speicher.
\n\nBeispiel zum Erstellen eines virtuellen Threads:
\n\npublic class VirtualThreadExample {\n public static void main(String[] args) throws InterruptedException {\n // Erstellen von 100.000 virtuellen Threads\n var threads = new Thread[100_000];\n for (int i = 0; i < threads.length; i++) {\n threads[i] = Thread.startVirtualThread(() -> {\n // Simulation von Arbeit mit blockierendem I/O\n try {\n Thread.sleep(1000);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n });\n }\n for (Thread t : threads) {\n t.join();\n }\n System.out.println("Alle virtuellen Threads sind abgeschlossen!");\n }\n}\n\nFantastisch, nicht wahr?