⌘ Thema: Java Threads
Java Threads verstehen: Plattform-Threads, Virtual Threads und sichere Nebenläufigkeit
Von Guido Zeuner · Softwareentwickler und Autor von tiny-tool.de
Threads ermöglichen mehreren Aufgaben, innerhalb eines Java-Prozesses nebenläufig zu arbeiten. Entscheidend ist dabei nicht nur, wie du einen Thread startest, sondern wie du Aufgaben trennst, gemeinsamen Zustand schützt und zwischen Plattform- und Virtual Threads auswählst.
Das Wichtigste vorab
Kurzfassung: ca. 1 Minute · vollständiger Artikel: ca. 9 Minuten
- Ein Thread ist ein Ausführungspfad innerhalb eines Prozesses. Threads teilen sich den Heap und können deshalb gemeinsam auf Objekte zugreifen.
start()plant einen neuen Thread zur Ausführung ein; ein direkter Aufruf vonrun()bleibt im aktuellen Thread.Runnablebeschreibt eine Aufgabe. In Anwendungsprogrammen ist diese Trennung meist flexibler als eine eigene Unterklasse vonThread.- Gemeinsam veränderter Zustand braucht eine klare Synchronisationsstrategie, etwa Unveränderlichkeit,
synchronized, Locks oder atomare Klassen. - Executors verwalten Aufgaben und Threads. Virtual Threads eignen sich besonders für sehr viele blockierende I/O-Aufgaben, beschleunigen aber keine CPU-intensive Berechnung.
- Unterbrechung ist kooperativ: Ein Thread sollte auf
InterruptedExceptionsinnvoll reagieren und den Interrupt nicht still verschlucken.

Nebenläufigkeit wird schnell missverständlich: Zwei Aufgaben können sich zeitlich überlappen, ohne in jedem Moment wirklich parallel auf verschiedenen Prozessorkernen zu laufen. Gleichzeitig bringt das Teilen von Daten neue Fehlerklassen mit sich. Dieser Guide führt deshalb vom ersten Thread über Zustände und Synchronisation bis zu Executor Services, Virtual Threads und einem kurzen Kotlin-Vergleich.
Prozess, Thread und Nebenläufigkeit
Ein laufendes Java-Programm ist ein Prozess. Innerhalb dieses Prozesses können mehrere Threads existieren. Jeder Thread besitzt unter anderem einen eigenen Aufrufstapel, aber die Threads eines Prozesses teilen sich den Heap. Dadurch können sie dieselben Objekte sehen und verändern.
Dieses gemeinsame Arbeiten ist leistungsfähig, aber nicht automatisch sicher. Wenn zwei Threads denselben Wert gleichzeitig lesen und schreiben, kann das Ergebnis von der zufälligen zeitlichen Reihenfolge abhängen. Solche Fehler sind häufig schwer reproduzierbar.
| Begriff | Bedeutung | Beispiel |
|---|---|---|
| Nebenläufigkeit | Mehrere Aufgaben machen im selben Zeitraum Fortschritt. | Eine Aufgabe wartet auf eine Datei, während eine andere eine Anfrage vorbereitet. |
| Parallelität | Mehrere Aufgaben werden im selben Moment auf unterschiedlichen Recheneinheiten ausgeführt. | Zwei CPU-intensive Berechnungen laufen auf zwei Prozessorkernen. |
Mehr Threads bedeuten nicht automatisch mehr Geschwindigkeit. Bei CPU-intensiver Arbeit begrenzen die verfügbaren Kerne den sinnvollen Parallelismus; zu viele Plattform-Threads erzeugen zusätzlich Verwaltungs- und Kontextwechselkosten.
Die Zustände eines Java-Threads
Die JVM beschreibt den Zustand eines Threads mit Thread.State. Diese Zustände sind JVM-Zustände und nicht identisch mit den Zuständen, die ein Betriebssystem intern verwendet.
| Zustand | Bedeutung |
|---|---|
NEW |
Das Thread-Objekt wurde erzeugt, aber noch nicht gestartet. |
RUNNABLE |
Der Thread läuft in der JVM oder wartet darauf, Rechenzeit beziehungsweise eine Betriebssystemressource zu erhalten. |
BLOCKED |
Der Thread wartet auf den Monitor eines synchronized-Bereichs. |
WAITING |
Der Thread wartet ohne Zeitgrenze auf eine Aktion eines anderen Threads, etwa bei join(). |
TIMED_WAITING |
Der Thread wartet höchstens bis zu einem bestimmten Zeitpunkt, etwa bei sleep(). |
TERMINATED |
Die Methode run() ist normal oder mit einer nicht behandelten Ausnahme beendet. |
Einen Thread richtig starten
Für eine einzelne Aufgabe genügt ein Runnable. Da es ein funktionales Interface ist, kannst du eine Lambda-Expression verwenden. Der moderne Thread-Builder macht zugleich sichtbar, ob du einen Plattform- oder Virtual Thread erzeugst.
public class ThreadStartDemo {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> System.out.println(
"Läuft in: " + Thread.currentThread().getName());
Thread worker = Thread.ofPlatform()
.name("report-worker")
.start(task);
worker.join();
System.out.println("Aufgabe beendet");
}
}
Der Haupt-Thread wartet mit join(), bis worker beendet ist. Ohne dieses Warten kann die Reihenfolge der Ausgaben variieren. Ein nicht gestarteter Thread kann nur einmal mit start() gestartet werden.
start(), run() und join()
start()
Plant den Thread zur Ausführung ein. Die JVM ruft dessen run()-Methode in einem neuen Ausführungspfad auf.
run()
Ist ein normaler Methodenaufruf, wenn du ihn direkt aufrufst. Dadurch entsteht kein neuer Thread.
join()
Lässt den aufrufenden Thread warten, bis der andere Thread beendet ist oder die angegebene Wartezeit abläuft.
sleep()
Pausiert den aktuell laufenden Thread zeitlich. Die Methode ist kein Werkzeug zur zuverlässigen Koordination.
Keine Reihenfolge mit sleep() erzwingen
Eine feste Wartezeit beweist nicht, dass eine andere Aufgabe fertig ist. Verwende für Abhängigkeiten beispielsweise join(), Future.get(), Latches oder höherwertige Nebenläufigkeits-APIs.
Gemeinsamen Zustand sicher behandeln
Die Operation counter++ besteht aus Lesen, Erhöhen und Schreiben. Zwei Threads können denselben alten Wert lesen und anschließend beide denselben neuen Wert speichern. Ein Update geht dann verloren. Das Schlüsselwort volatile macht eine zusammengesetzte Operation nicht atomar.
import java.util.concurrent.atomic.AtomicInteger;
public final class RequestCounter {
private final AtomicInteger value = new AtomicInteger();
public int incrementAndGet() {
return value.incrementAndGet();
}
public int currentValue() {
return value.get();
}
}
AtomicInteger passt zu einfachen atomaren Aktualisierungen. Sobald mehrere Werte gemeinsam eine Invariante bilden, brauchst du meist eine umfassendere Strategie.
- Unveränderlichkeit: Geteilte Objekte können nach ihrer Konstruktion nicht mehr verändert werden.
- Thread-Beschränkung: Veränderlicher Zustand gehört genau einem Thread.
synchronizedoder Lock: Zusammengehörige Zugriffe werden als kritischer Abschnitt geschützt.- Concurrent Collections: Klassen wie
ConcurrentHashMapstellen geeignete atomare Operationen bereit.
Aufgaben mit ExecutorService ausführen
Direkt erzeugte Threads sind für Lernbeispiele hilfreich. In Anwendungen sollte der Code, der eine Aufgabe beschreibt, aber häufig nicht selbst entscheiden, wie viele Threads entstehen. Ein ExecutorService trennt Einreichen, Ausführen und Beenden von Aufgaben.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorDemo {
public static void main(String[] args) throws Exception {
try (ExecutorService executor = Executors.newFixedThreadPool(4)) {
Future result = executor.submit(() -> 21 * 2);
System.out.println(result.get()); // 42
}
}
}
Runnable liefert kein Ergebnis. Callable darf dagegen einen Wert zurückgeben und eine Exception auslösen. Future.get() wartet auf das Ergebnis und macht einen Fehlschlag über eine ExecutionException sichtbar.
Ein ExecutorService ist AutoCloseable. Der try-with-resources-Block leitet deshalb ein geordnetes Herunterfahren ein. Bei langlebigen Diensten gehört die Lebensdauer des Executors zur Lebensdauer der Anwendungskomponente.
Virtual Threads sinnvoll einsetzen
Java kennt Plattform-Threads und Virtual Threads. Ein Plattform-Thread ist typischerweise eng an einen Betriebssystem-Thread gebunden. Ein Virtual Thread wird von der Java-Laufzeit verwaltet und kann beim blockierenden Warten von seinem sogenannten Carrier Thread gelöst werden.
Dadurch kann eine Anwendung sehr viele überwiegend wartende Aufgaben in einem einfachen Thread-pro-Aufgabe-Stil ausführen. Das erhöht bei passenden Server-Workloads den Durchsatz, nicht automatisch die Geschwindigkeit einer einzelnen Anfrage.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VirtualThreadDemo {
public static void main(String[] args) throws Exception {
try (ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()) {
var first = executor.submit(() -> loadCustomer(17));
var second = executor.submit(() -> loadOrders(17));
System.out.println(first.get());
System.out.println(second.get());
}
}
private static String loadCustomer(int id) {
return "Customer " + id; // Stellvertretend für blockierende I/O
}
private static String loadOrders(int customerId) {
return "Orders for " + customerId;
}
}
| Aspekt | Plattform-Thread | Virtual Thread |
|---|---|---|
| Verwaltung | Typischerweise eng an einen Betriebssystem-Thread gebunden | Von der Java-Laufzeit auf Carrier Threads geplant |
| Guter Einsatz | CPU-Arbeit, wenige langlebige Worker, spezielle Thread-Eigenschaften | Sehr viele überwiegend blockierende I/O-Aufgaben |
| Anzahl | Begrenzte Ressource | Sehr große Anzahl möglich |
| Priorität | Konfigurierbar, aber selten eine gute Steuerungsstrategie | Feste normale Priorität |
| Daemon-Status | Daemon oder Non-Daemon | Immer Daemon |
Virtual Threads sind kein größerer Thread-Pool. Sie sollten normalerweise nicht gepoolt werden. Begrenze stattdessen ausdrücklich die knappe Zielressource, etwa über einen Semaphore für eine externe Schnittstelle.
Unterbrechung und kooperativer Abbruch
interrupt() beendet einen Thread nicht gewaltsam. Es setzt ein Unterbrechungssignal. Blockierende Methoden wie sleep(), join() oder viele Queue-Operationen reagieren darauf mit InterruptedException.
try {
processNextItem();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
Kann die Methode die Exception nicht weitergeben, stellt sie den Interrupt-Status in der Regel wieder her. Dadurch können aufrufende Ebenen erkennen, dass ein Abbruch gewünscht ist. Die veralteten Methoden stop(), suspend() und resume() sind keine sichere Alternative.
Daemon Threads und Programmende
Die JVM beginnt mit dem Herunterfahren, wenn alle gestarteten Non-Daemon-Plattform-Threads beendet sind. Daemon-Threads sind deshalb nur für unterstützende Arbeit geeignet, die beim Programmende nicht zuverlässig abgeschlossen werden muss. Virtual Threads sind immer Daemon-Threads.
Keine wichtigen Daten nur einem Daemon-Thread anvertrauen
Ein Daemon-Thread garantiert nicht, dass ein Schreibvorgang, ein Log-Export oder eine Sicherung abgeschlossen wird. Für wichtige Abschlussarbeit brauchst du eine kontrollierte Lebensdauer und gegebenenfalls einen Shutdown-Hook.
Java und Kotlin: Threads und Coroutines
Kotlin läuft auf der JVM und kann dieselben Java-Thread- und Executor-APIs verwenden. Die Standardbibliothek bietet mit kotlin.concurrent.thread eine kompakte Hilfsfunktion. Häufiger setzen Kotlin-Anwendungen jedoch Coroutines ein.
| Aspekt | Java | Kotlin |
|---|---|---|
| Aufgabe ohne Ergebnis | Runnable |
Lambda beziehungsweise suspendierende Funktion |
| Thread-Erzeugung | Thread.Builder oder Executor |
Java-API oder thread { ... } |
| Leichtgewichtige Nebenläufigkeit | Virtual Threads | Coroutines |
| Abbruch | Kooperativ über Interrupts und API-Verträge | Kooperativ über Coroutine-Cancellation |
Eine Coroutine ist nicht einfach ein anderer Name für einen JVM-Thread. Coroutines werden durch eine Bibliothek geplant und können suspendieren, ohne einen Thread zu blockieren. Virtual Threads bleiben dagegen vollständige Thread-Objekte mit den bekannten Java-Regeln.
Best Practices und typische Anti-Patterns
Gute Leitlinien
- Aufgabe und Ausführungsmechanismus trennen.
- Executors kontrolliert schließen.
- Threads und Aufgaben aussagekräftig benennen.
- Geteilten veränderlichen Zustand minimieren.
- Interrupts und Abbruchpfade testen.
- Virtual Threads für passende I/O-Workloads einsetzen.
Warnsignale
run()wird stattstart()aufgerufen.sleep()soll eine Reihenfolge erzwingen.- Exceptions und Interrupts werden verschluckt.
- Für jede kleine Aufgabe wird ungeplant ein Plattform-Thread erzeugt.
- Gemeinsame Collections werden ohne Synchronisationskonzept verändert.
- Virtual Threads sollen CPU-Arbeit beliebig beschleunigen.
Thread-Glossar
- Thread
- Ausführungspfad innerhalb eines Prozesses; in Java eine Instanz von
Thread. - Plattform-Thread
- Java-Thread, der typischerweise eng an einen Betriebssystem-Thread gebunden ist.
- Virtual Thread
- Leichtgewichtiger Java-Thread, den die Laufzeit auf einer kleineren Zahl von Carrier Threads plant.
- Race Condition
- Fehler, dessen Ergebnis von einer nicht zuverlässig kontrollierten zeitlichen Reihenfolge abhängt.
- Executor
- Komponente, die Aufgaben entgegennimmt und ihre Ausführung organisiert.
- Interrupt
- Kooperatives Signal, das einen Abbruch oder eine Unterbrechung anfordert.
- Atomare Operation
- Operation, die aus Sicht anderer Threads nicht teilweise beobachtet wird.
Fazit: Gute Nebenläufigkeit beginnt bei klaren Verantwortlichkeiten
Threads sind nicht nur eine Möglichkeit, Code gleichzeitig auszuführen. Sie verändern die Regeln für Sichtbarkeit, Reihenfolge, Fehlerbehandlung und Programmende. Deshalb sollte eine Anwendung Aufgaben von ihrer Ausführung trennen und gemeinsam veränderlichen Zustand bewusst begrenzen.
Die praktische Leitlinie: Beginne mit einem Runnable oder Callable, übergib die Aufgabe an einen passenden Executor und entscheide anhand des Workloads zwischen Plattform- und Virtual Threads. Erzeuge eigene Threads nur dann direkt, wenn ihre Lebensdauer und Verantwortung wirklich zur Anwendung passen.
Offizielle Quellen und weiterführende Informationen
Transparenzhinweis
Die Inhalte auf tiny-tool.de werden sorgfältig recherchiert, redaktionell geprüft und regelmäßig aktualisiert. Quellen und Zitate werden möglichst nachvollziehbar angegeben. Dennoch übernehmen wir keine Garantie für Richtigkeit, Vollständigkeit oder Aktualität der bereitgestellten Informationen. Irrtümer sind nicht ausgeschlossen.
Redaktion und Einsatz von KI: Bei der Erstellung von Inhalten können digitale Werkzeuge – darunter auch KI-basierte Assistenzsysteme – unterstützend eingesetzt werden, etwa bei Recherche, Strukturierung, sprachlicher Überarbeitung, Übersetzung, Codeanalyse oder visueller Gestaltung. Veröffentlichte Inhalte werden redaktionell geprüft, bearbeitet und von Guido Zeuner freigegeben. Auswahl, Einordnung und Veröffentlichung liegen beim Menschen. KI-Ausgaben gelten nicht als eigenständige Quellen. KI-Systeme sind keine verantwortlichen Autoren oder Redakteure. Weitere Informationen zu Texten, Bildern, Videos und digitalen Personas findest du auf unserer Seite Transparenz beim Einsatz von Künstlicher Intelligenz.
Reichweitenmessung (VG WORT / METIS): Zur Ermittlung der Reichweite einzelner Texte können Zählmarken der VG WORT eingesetzt werden. Im Rahmen der METIS-Zugriffszählung kann eine Client-ID gebildet und ein sogenanntes „METIS Session Cookie“ gesetzt werden. Die Messung dient der statistischen Ermittlung von Textzugriffen und als Grundlage für mögliche Ausschüttungen der VG WORT. Nach Angaben der VG WORT werden dabei keine personenbezogenen Nutzungsprofile erstellt; die Messung dient nicht der Werbung oder dem Marketing-Tracking. Weitere Informationen findest du in unseren Datenschutzhinweisen.
Bitte beachte: Die Inhalte dienen ausschließlich der allgemeinen Information und stellen keine fachliche Beratung dar, insbesondere keine rechtliche, steuerliche, medizinische, technische oder finanzielle Beratung. Die Nutzung der Inhalte erfolgt auf eigene Verantwortung.
Werbung und Affiliate-Links: Einige Beiträge können werbliche Hinweise oder sogenannte Affiliate-Links enthalten. Diese werden entsprechend gekennzeichnet. Beim Klick entstehen dir keine zusätzlichen Kosten; wir erhalten gegebenenfalls eine kleine Provision.
Markenrechtlicher Hinweis: Alle Markennamen, Logos und Produktbezeichnungen sind Eigentum der jeweiligen Rechteinhaber und werden ausschließlich zur Identifikation und Beschreibung verwendet. Eine Verbindung zu den genannten Unternehmen besteht nur, wenn dies ausdrücklich angegeben wird.
Externe Links: Diese Website enthält Verweise auf externe Websites Dritter. Trotz sorgfältiger Prüfung übernehmen wir keine Verantwortung für deren Inhalte. Bei Bekanntwerden rechtswidriger Inhalte werden entsprechende Links geprüft und gegebenenfalls entfernt.



tiny-tool.de
tiny-tool.de