Synchronized ist ein integrierter Synchronisationsmechanismus in der Sprache Java, der exklusiven Zugriff auf kritische Codeabschnitte bietet. Laut Oracle, 2024 garantiert der synchronized-Modifikator, dass nur ein Thread die markierte Methode oder den markierten Block zu einem bestimmten Zeitpunkt ausführen kann. Dieser Mechanismus basiert auf Monitoren — einem grundlegenden Konzept von Betriebssystemen, das die korrekte Funktionsweise von Multithread-Anwendungen aller Komplexitätsstufen gewährleistet.
Wichtige Punkte
Synchronized ist ein Schlüsselwort in Java, das garantiert, dass nur ein Thread zu jeder Zeit einen geschützten Codeabschnitt ausführt und so Datenkorruption bei gleichzeitigem Zugriff verhindert. Es erschien in der ersten Version von Java und bleibt der einfachste Weg, um Threadsicherheit für Entwickler aller Erfahrungsstufen zu gewährleisten.
Der synchronized-Modifikator löst zwei Aufgaben: gegenseitigen Ausschluss (mutual exclusion) und Sichtbarkeit von Änderungen. Wenn ein Thread einen synchronized-Block verlässt, sind alle Änderungen für andere Threads sichtbar, die einen auf demselben Objekt synchronisierten Block betreten.
Synchronized kann auf eine gesamte Methode oder auf einen beliebigen Codeblock unter Angabe eines Monitor-Objekts angewendet werden. In beiden Fällen fügt die JVM auf Bytecode-Ebene monitorenter- und monitorexit-Anweisungen ein.
In Multithread-Anwendungen ohne Synchronisation tritt eine Race Condition auf, wenn zwei Threads gleichzeitig dieselben Daten ändern, was zu unvorhersehbaren Ergebnissen führt. Synchronized wurde zum ersten und wichtigsten Werkzeug von Java zur Bekämpfung dieses Problems und bietet eine einfache deklarative Syntax, die jedem Entwickler zugänglich ist.
Der synchronized-Mechanismus basiert auf dem Konzept des Monitors — einem hochwertigen Synchronisationsprimitiv, das in jedes Java-Objekt eingebaut ist. Der Monitor wird einem Objekt zugeordnet, wenn erstmals ein synchronized-Block darauf verwendet wird.
Jedes Objekt in Java hat einen zugehörigen Monitor. Wenn ein Thread in einen synchronized-Block eintritt, erwirbt er den Monitor des Objekts. Wenn der Monitor bereits von einem anderen Thread gehalten wird, blockiert der Thread, bis er freigegeben wird. Im Bytecode entspricht dies dem Paar von monitorenter- und monitorexit-Anweisungen.
Die JVM optimiert synchronized über mehrere Ebenen: Biased Locking für Single-Thread-Zugriff, Lightweight Locking für geringe Konkurrenz und Heavyweight Locking für intensive Konkurrenz unter Beteiligung des Betriebssystems. Diese Ebenen verbessern die Leistung ohne Codeänderung.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Synchronized stellt eine Happens-before-Beziehung her: Alle Aktionen in einem Thread vor dem Verlassen eines synchronized-Blocks sind für einen anderen Thread sichtbar, nachdem er einen auf demselben Objekt synchronisierten Block betreten hat. Dies garantiert nicht nur gegenseitigen Ausschluss, sondern auch Datenkonsistenz für alle Threads.
Java bietet zwei Möglichkeiten, synchronized anzuwenden: auf Methodenebene und auf Blockebene. Die Wahl zwischen ihnen beeinflusst die Leistung und die Granularität der Synchronisation.
Das Markieren einer Methode mit dem synchronized-Modifikator synchronisiert sie automatisch auf der aktuellen Instanz (für Instanzmethoden) oder auf dem Class-Objekt (für statische Methoden). Dies ist der einfachste Weg, gegenseitigen Ausschluss zu gewährleisten, aber oft überzogen, wenn der kritische Abschnitt nur einen kleinen Teil der Methode ausmacht und der restliche Code keine Synchronisation benötigt.
Ein synchronized-Block gibt präzise Kontrolle: Sie geben das Monitor-Objekt an und synchronisieren nur den notwendigen Codeabschnitt, während der Rest der Methode außerhalb der Sperre bleibt. Dies minimiert die Haltezeit des Monitors und verbessert die Gesamtleistung der Anwendung in einer Multithread-Umgebung, da andere Threads nicht auf die Freigabe des Monitors warten müssen, um nicht verwandten Code parallel auszuführen.
class DataProcessor {
private final Object lock = new Object();
public void process() {
// Code außerhalb des kritischen Abschnitts - ohne Synchronisation
prepareData()
synchronized (lock) {
// nur dieser Block ist geschützt
updateSharedState()
}
// Fortsetzung ohne Sperre
cleanup()
}
}
| Kriterium | Synchronized-Methode | Synchronized-Block |
|---|---|---|
| Monitor | this (Instanz) oder Class | beliebiges Objekt |
| Granularität | gesamte Methode | nur benötigter Code |
| Lesbarkeit | hoch | mittel |
| Leistung | niedriger bei großen Methoden | höher bei kleinen kritischen Abschnitten |
In der Android-Entwicklung wird synchronized häufig zum Schutz von SharedPreferences, Datenbankzugriffen und UI-Komponenten verwendet. Die Verwendung im Hauptthread wird jedoch aufgrund des Risikos von Einfrieren der Benutzeroberfläche dringend vermieden.
SharedPreferences in Android bietet grundlegende Threadsicherheit, aber bei Bearbeitung durch mehrere Threads über den Editor kann eine externe Synchronisation erforderlich sein. Ein synchronized-Block mit einem separaten Sperrobjekt garantiert die Konsistenz der Änderungen.
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
Die Haupteinschränkung von synchronized auf Android ist die Thread-Blockierung. Im Gegensatz zu Coroutinen mit Mutex blockiert synchronized den gesamten Systemthread. Im Hauptthread verursacht dies ANR. In der modernen Android-Entwicklung wird empfohlen, synchronized durch Coroutinen (suspend Mutex) oder atomare Typen (AtomicInteger) zu ersetzen.
Modernes Java und Kotlin bieten mehrere Alternativen zu synchronized, die jeweils die gleichen Probleme mit weniger Einschränkungen oder besserer Leistung lösen.
Das Lock-Interface mit den Implementierungen ReentrantLock und ReadWriteLock bietet Timeouts, unterbrechbares Warten und mehrere Condition-Warteschlangen. Es ist flexibler als synchronized, erfordert aber explizite Freigabe in finally, was das Risiko von Fehlern bei vergessenem unlock erhöht.
AtomicInteger, AtomicLong, AtomicReference und andere Klassen verwenden Lock-Free-Algorithmen auf Basis von CAS (Compare-And-Swap). Sie sind in Szenarien mit moderater Konkurrenz deutlich schneller als synchronized, da sie Threads nicht blockieren, sondern optimistische Wiederholungen ohne Kontextwechsel des OS-Kernels durchführen.
ThreadLocal bietet einen alternativen Ansatz: Jede ThreadLocal-Variable ist innerhalb eines einzelnen Threads isoliert und erfordert keine Synchronisation für Lese- und Schreibzugriffe. Dies beseitigt vollständig die Notwendigkeit von synchronized für Daten, die nicht zwischen Threads geteilt werden sollen. ThreadLocal wird aktiv in Frameworks (Spring, Hibernate) zur Speicherung von Transaktionskontexten und Sitzungen verwendet.
In Kotlin-Projekten für Android dient Mutex aus kotlinx.coroutines als Alternative zu synchronized. Es blockiert nicht den Betriebssystem-Thread, sondern setzt die Coroutine bis zur Freigabe der Sperre aus — dies ermöglicht eine effiziente Nutzung der Pool-Threads und vermeidet ANR bei langen Wartezeiten auf die Ressourcenfreigabe.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
Die Leistung von synchronized hat sich in den letzten Java-Versionen deutlich verändert. Früher galt es als „schwerer“ Mechanismus, aber moderne JVMs haben die meisten Gemeinkosten durch fortschrittliche JIT-Compiler-Optimierungen beseitigt. Sehen wir uns im Detail an, wie die virtuelle Maschine synchronisierten Code zur Laufzeit beschleunigt.
Der JIT-Compiler der JVM wendet mehrere Optimierungen an: Biased Locking eliminiert die Synchronisation, wenn die Sperre immer vom selben Thread erworben wird; Lock Coarsening fasst benachbarte synchronized-Blöcke zu einem zusammen; Lock Elimination entfernt die Synchronisation, wenn das Objekt nur von einem Thread zugänglich ist. Diese Optimierungen machen synchronized bei geringer Konkurrenz praktisch kostenlos.
Die JVM bestimmt den Konkurrenzgrad für jedes Objekt: Bei keiner Konkurrenz wird Biased Locking aktiviert; beim Auftreten eines zweiten Threads wechselt die Sperre in den Lightweight-Modus mit Spin-Warten; und nur bei längerem Warten eskaliert sie zu Heavyweight mit einem System-Mutex. Diese Eskalation erfolgt automatisch, und der Entwickler muss nicht manuell eine Strategie wählen.
In modernen Benchmarks (Java 17+) zeigt synchronized eine mit ReentrantLock vergleichbare Leistung bei niedriger und moderater Konkurrenz. Bei hoher Konkurrenz kann Lock dank einer effizienteren Warteschlange mit Timeout- und Interrupt-Unterstützung Vorteile haben. Für hochlastige Systeme mit konstanter Konkurrenz bietet ReentrantLock mit Fair-Mode ein vorhersehbareres Verhalten.
Atomare Klassen (AtomicInteger, AtomicReference) bleiben dank der Lock-Free-CAS-Implementierung die schnellsten für einfache Zähler und Flags. Sie blockieren Threads überhaupt nicht — bei Konflikten wiederholt die Operation einfach in einer Schleife. Dies ergibt einen 3- bis 5-fachen Leistungsgewinn im Vergleich zu synchronized bei Zählererhöhungsoperationen mit 4-8 Threads.
Häufig gestellte Fragen
Synchronized bietet sowohl gegenseitigen Ausschluss als auch Sichtbarkeit. Volatile garantiert nur die Sichtbarkeit von Änderungen — das Schreiben in eine volatile-Variable ist für alle Threads sichtbar, verhindert aber nicht die gleichzeitige Änderung, schützt also nicht vor Race Conditions.
Ja, Deadlock ist bei verschachtelter Synchronisation mit unterschiedlicher Monitor-Reihenfolge möglich. Beispielsweise ruft ein Thread synchronized(a) { synchronized(b) } auf, während ein anderer synchronized(b) { synchronized(a) } aufruft. Vermeiden Sie verschachtelte synchronized-Blöcke oder legen Sie eine konsistente Monitor-Reihenfolge fest.
Ein Monitor ist ein Synchronisationsmechanismus, der mit jedem Java-Objekt verbunden ist. Er garantiert, dass nur ein Thread synchronized-Code auf diesem Objekt ausführt. Der Monitor umfasst eine Sperre, eine Warteschlange und einen Pool von Threads, die auf Benachrichtigung über wait/notify warten.
In modernen Java-Versionen (17+) steht synchronized Lock in Bezug auf die Leistung dank JIT-Optimierungen (Biased Locking, Lock Coarsening) nicht nach. Lock wird nicht wegen der Geschwindigkeit bevorzugt, sondern wegen zusätzlicher Funktionen: Timeouts, unterbrechbares Warten und mehrere Condition-Warteschlangen.
Eine statische synchronized-Methode verwendet den Monitor des Class-Objekts der gegebenen Klasse, nicht der Instanz. Das bedeutet, dass die Synchronisation für alle Instanzen der Klasse gilt. Nicht-statische und statische synchronized-Methoden verwenden unterschiedliche Monitore und blockieren sich nicht gegenseitig.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch