Java-Thread-Architektur und Synchronisation: Vom CPU-Kern zur JVM
Java-Code wird zu Bytecode kompiliert, den die JVM in Maschinenbefehle für den Prozessor umwandelt. Jeder CPU-Kern führt Befehle sequentiell aus: Eine einfache Operation wie ein Bit-Shift in einem Register passiert in einem Taktzyklus. Register sind ultraschnelle Speicherzellen für Zahlen, Adressen und Daten.
Moderne Prozessoren nutzen Superskalar-Architekturen. Hyper-Threading ermöglicht es einem Hardware-Thread, 2–6 Befehle pro Zyklus auszuführen und erzeugt so den Eindruck mehrerer logischer Threads pro Kern durch schnelles Kontext-Switchen.
Die Anzahl gleichzeitig laufender Threads ist durch die Kernanzahl begrenzt. Threads teilen sich den Heap-Speicher für Objekte, während jeder seinen eigenen Stack für lokale Variablen und Objektreferenzen hat.
Race Conditions und Speicherstruktur
Eine Race Condition entsteht, wenn mehrere Threads gleichzeitig denselben Heap-Objekt lesen und modifizieren. Das Ergebnis ist unvorhersehbar und hängt von der Reihenfolge der Lese-Ändere-Schreibe-Operationen ab.
Code läuft lokal oft fehlerfrei ohne Last, aber in der Produktion unter Multithreading tauchen Kollisionen unregelmäßig auf. Ein OS-Prozess reserviert gemeinsamen Speicher; Threads darin sind leichte Ausführungseinheiten, die um Ressourcen konkurrieren.
Synchronisationsmechanismen: wait/notify und Monitore
Jedes Java-Objekt hat einen integrierten Monitor (Lock). Der synchronized-Block erobert den Monitor des Objekts und gewährleistet:
- Mutual Exclusion: Nur ein Thread führt den Code im Block aus.
- Visibility: Änderungen sind nach Verlassen des Blocks für andere Threads sichtbar (mit
volatile-Unterstützung).
Der Monitor umfasst:
- Entry Set: Warteschlange der Threads, die ihn erobern wollen.
- Wait Set: Threads im Wartezustand.
wait(), notify() und notifyAll() funktionieren nur innerhalb eines synchronized-Blocks am selben Objekt – sonst gibt's IllegalMonitorStateException.
wait(): Verschiebt den Thread in den Wait Set.notify(): Weckt einen zufälligen Thread (Risiko verlorener Wakeups).notifyAll(): Weckt alle Threads (bevorzugte Methode).
Statische synchronized-Methoden sperren den Klassenmonitor (Class), ohne mit Instanzmethoden (this) zu kollidieren.
Erweiterte Locks: ReentrantLock und Semaphore
Für mehr Kontrolle: java.util.concurrent.locks.ReentrantLock:
Lock lock = new ReentrantLock();
lock.lock();
try {
// kritischer Abschnitt
} finally {
lock.unlock();
}
Wichtige Methoden:
tryLock(): Erwirbt ohne Warten (false bei Misserfolg).tryLock(timeout, unit): Mit Timeout.lockInterruptibly(): Unterbrechbar.new ReentrantLock(true): Fairer Lock (FIFO, langsamer).
Semaphore begrenzt den Zugriff:
Semaphore sem = new Semaphore(3); // 3 Permits
sem.acquire(); // erwerben
sem.release(); // freigeben
Multithreading-Fallen: Deadlock und Livelock
Deadlock: Zyklische Lock-Abhängigkeiten. Thread A hält Lock1 und wartet auf Lock2; Thread B umgekehrt. Das System friert ein.
Vermeiden durch:
- Feste Lock-Reihenfolge.
- Timeouts.
- Minimale Verschachtelung.
Livelock: Threads sind beschäftigt, kommen aber nicht voran. Beispiel: Zwei Threads geben sich höflich in einer Schleife Ressourcen frei, CPU rastet durch, ohne Fortschritt.
Synchron vs. Asynchron in Threads
Synchron blockiert den Thread bei I/O (z. B. DB-Abfrage): Der Thread faulenzt bis zur Antwort.
Asynchron mit CompletableFuture – Container für verzögerte Ergebnisse (pending, completed oder failed).
Ketten:
thenApply(): Ergebnis transformieren.thenCompose(): Mit anderem Future kombinieren.thenAccept(): Verbrauchen ohne Rückgabe.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "result")
.thenApply(s -> s.toUpperCase())
.thenAccept(System.out::println);
Das entlastet den Thread für andere Aufgaben und steigert den Durchsatz in Multithreading-Umgebungen.
Wichtige Erkenntnisse
- Jeder Kern führt einen Hardware-Thread aus; Hyper-Threading simuliert mehrere.
- Race Conditions entstehen durch geteilten Heap; Synchronisation sichert Mutual Exclusion und Visibility.
notifyAll()stattnotify()bevorzugen, um verlorene Wakeups zu vermeiden.- ReentrantLock erweitert
synchronizedum Timeouts, Interrupts und Fairness. - CompletableFuture ermöglicht asynchrone Pipelines ohne Thread-Blockade.
— Editorial Team
Noch keine Kommentare.