Lock: Was es ist, Arten von Sperren und Verwendung in der Synchronisation

Autor: IT Sectr Veröffentlicht: 2026-03-19 Lesezeit: 8 Min.

Lock ist ein Synchronisationsmechanismus, der exklusiven Zugriff auf kritische Codeabschnitte in Multithread-Anwendungen bietet. Laut Oracle, 2024 bietet die Lock-Schnittstelle eine flexiblere Synchronisationskontrolle im Vergleich zu traditionellen synchronized-Blöcken, einschließlich Erfassungsversuchen mit Timeout und Unterstützung mehrerer Warteschlangen.

Wichtige Punkte

  • Lock ist eine Schnittstelle zur expliziten Sperrverwaltung in Java.
  • ReentrantLock ist eine grundlegende Implementierung mit Unterstützung für Wiedererfassung durch denselben Thread.
  • ReadWriteLock trennt Lese- und Schreibsperren für bessere Leistung.
  • Deadlock ist das Hauptrisiko bei der gleichzeitigen Verwendung mehrerer Sperren.
  • Im Gegensatz zu synchronized unterstützt Lock Timeouts und unterbrechbares Warten.

Was ist Lock?

Lock ist eine Schnittstelle aus dem Paket java.util.concurrent.locks, die explizite Sperr- und Entsperroperationen zur Synchronisation des Datenzugriffs bereitstellt. Im Gegensatz zu synchronized gibt Lock dem Entwickler die vollständige Kontrolle über den Sperrmechanismus.

Definition und Rolle in der Synchronisation

Die Lock-Schnittstelle wurde in Java 5 als Alternative zum integrierten synchronized-Mechanismus eingeführt. Die wichtigsten Methoden sind lock, unlock, tryLock und lockInterruptibly. Sperren ermöglichen einen sicheren Datenzugriff in einer Multithread-Umgebung und verhindern Wettlaufsituationen und Datenkorruption.

Der Hauptvorteil von Lock gegenüber synchronized ist die Flexibilität. Der Entwickler kann versuchen, eine Sperre mit Timeout zu erwerben, ihre Verfügbarkeit ohne Blockierung prüfen oder mehrere Warteschlangen mit unterschiedlichen Prioritäten organisieren.

Geschichte und Entwicklung

Bevor die Lock-Schnittstelle in Java 5 erschien, war die einzige Synchronisationsmethode synchronized, das unter Einschränkungen litt: keine Timeouts, kein unterbrechbares Warten und eine einzige Warteschlange. Doug Lea entwarf das Paket java.util.concurrent und nahm Lock als grundlegenden Baustein auf.

Wie funktioniert eine Sperre?

Eine Sperre verwaltet den Zugriff über ein internes Zustandsflag und eine Warteschlange. Wenn ein Thread lock() aufruft, prüft der Mechanismus, ob die Sperre frei ist, und erwirbt sie entweder oder reiht den Thread ein, bis sie freigegeben wird.

Atomare Erfassung und Freigabe

Im Kern jeder Sperre liegt eine atomare Compare-and-Swap (CAS)-Operation. Beim Aufruf von lock() versucht der Thread, das Belegt-Flag atomar zu setzen. Ist das Flag bereits gesetzt, wird der Thread blockiert. Bei unlock() wird das Flag gelöscht und ein wartender Thread wird aufgeweckt.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // kritischer Abschnitt
        println("Thread ${Thread.currentThread().name} arbeitet")
    } finally {
        lock.unlock()
    }
}

Warteschlange und Aufweckung

ReentrantLock verwendet intern eine doppelt verkettete Liste (CLH-Sperrwarteschlange), in der jeder wartende Thread durch einen Knoten dargestellt wird. Wenn die Sperre freigegeben wird, wird der Kopfknoten der Warteschlange aufgeweckt. Der faire Modus garantiert FIFO-Reihenfolge, während der unfaire Modus einem neuen Thread erlaubt, die Sperre vor wartenden Threads zu erwerben, um den Durchsatz zu erhöhen.

Hauptarten von Sperren

Im modernen Java-Ökosystem gibt es mehrere Sperren-Implementierungen, die jeweils für bestimmte Szenarien optimiert sind. Die Wahl der richtigen Sperre wirkt sich direkt auf die Leistung und Zuverlässigkeit einer Multithread-Anwendung aus.

ReentrantLock

ReentrantLock ist die grundlegende und am häufigsten verwendete Lock-Implementierung. Sie unterstützt die Wiedererfassung durch denselben Thread: Wenn ein Thread die Sperre bereits hält, blockiert ein erneuter Aufruf von lock() nicht. Dies verhindert Deadlocks bei rekursiven Aufrufen.

ReentrantReadWriteLock

ReadWriteLock trennt Sperren in zwei Modi: Lesen und Schreiben. Mehrere Threads können die Lesesperre gleichzeitig halten, aber Schreiben erfordert exklusiven Zugriff. Dies verbessert die Leistung bei häufigem Lesen und seltenem Schreiben erheblich.

StampedLock

StampedLock ist die neueste Implementierung, die in Java 8 eingeführt wurde. Sie unterstützt drei Modi: Schreiben, Lesen und optimistisches Lesen. Optimistisches Lesen blockiert andere Threads nicht und validiert die Daten nach dem Lesen, was eine Leistungssteigerung von 10-20% gegenüber ReadWriteLock bietet.

SperreJava-VersionModiLeistung
ReentrantLockJava 5exklusivhoch
ReadWriteLockJava 5Lesen + Schreibenmittel
StampedLockJava 8Lesen + Schreiben + optimistischsehr hoch

ReentrantLock und seine Eigenschaften

ReentrantLock ist die beliebteste Lock-Implementierung und bietet mehrere Funktionen, die in synchronized nicht verfügbar sind. Das Verständnis ihrer Eigenschaften ist für effektive Multithread-Arbeit unerlässlich.

Sperrfairness

Der ReentrantLock-Konstruktor akzeptiert einen fair-Parameter. Bei true garantiert die Sperre FIFO-Reihenfolge; bei false kann ein neuer Thread die Sperre vor wartenden Threads erwerben. Der faire Modus verhindert Aushungerung, reduziert aber den Durchsatz um 10-20% aufgrund des Overheads der Warteschlangenverwaltung.

Timeouts und unterbrechbares Warten

Im Gegensatz zu synchronized unterstützt ReentrantLock tryLock mit Timeout. Wenn die Sperre nicht innerhalb der angegebenen Zeit erworben werden kann, setzt der Thread die Ausführung fort, anstatt unbegrenzt zu blockieren. Die Methode lockInterruptibly ermöglicht es, einen wartenden Thread über Thread.interrupt() zu unterbrechen.

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Sperre erworben")
        } finally {
            lock.unlock()
        }
    } else {
        println("Sperre konnte nicht erworben werden")
    }
}

Bedingungen (Conditions)

ReentrantLock unterstützt mehrere Bedingungsvariablen über die Methode newCondition(). Jede Condition hat ihre eigene Warteschlange, was komplexe Aufweckszenarien ermöglicht. Die Methoden await() und signal() ersetzten wait() und notify() aus synchronized-Blöcken, aber mit Unterstützung für mehrere Warteschlangen.

ReadWriteLock und StampedLock

ReadWriteLock und StampedLock adressieren die Optimierung des Zugriffs, wenn Lesevorgänge gegenüber Schreibvorgängen überwiegen. Sie sind in Szenarien, in denen Lesen häufiger vorkommt als Schreiben, deutlich effizienter als ReentrantLock.

ReadWriteLock in der Praxis

Die ReadWriteLock-Schnittstelle enthält zwei Methoden: readLock() und writeLock(). Die Lesesperre kann von mehreren Threads gleichzeitig gehalten werden, während die Schreibsperre exklusiv ist. Ein typisches Beispiel ist ein threadsicherer Cache: Viele Threads lesen Daten, während nur einer sie regelmäßig aktualisiert.

kotlin
class SafeCache<K, V> {
    private val map = mutableMapOf<K, V>()
    private val rwLock = ReentrantReadWriteLock()

    fun get(key: K): V? {
        rwLock.readLock().lock()
        return try { map[key] } finally { rwLock.readLock().unlock() }
    }

    fun put(key: K, value: V) {
        rwLock.writeLock().lock()
        return try { map[key] = value } finally { rwLock.writeLock().unlock() }
    }
}

StampedLock und optimistisches Lesen

StampedLock fügt einen dritten Modus hinzu — tryOptimisticRead. Dieser Modus blockiert andere Threads nicht, sondern zeichnet lediglich einen Zustandsstempel (stamp) auf. Nach dem Lesen ruft der Entwickler validate(stamp) auf, um zu prüfen, ob sich die Daten während des Lesens geändert haben. Wenn sich die Daten geändert haben, muss der Vorgang wiederholt werden.

Sperren in der mobilen Entwicklung

In mobilen Anwendungen werden Sperren verwendet, um den Zugriff auf gemeinsam genutzte Daten zwischen Threads zu koordinieren. Ihre Verwendung erfordert jedoch besondere Vorsicht aufgrund der begrenzten Geräteressourcen und der Notwendigkeit, die UI-Reaktionsfähigkeit zu erhalten.

Sperren in Android (Kotlin)

Auf Android ist ReentrantLock bei der Arbeit mit Room, Caches und Dateien nützlich. Wichtig zu beachten: Erwerben Sie niemals eine Sperre im Hauptthread. Für asynchronen Code sind Koroutinen und Mutex aus kotlinx.coroutines vorzuziehen, da sie die Koroutine aussetzen, anstatt den Thread zu blockieren.

Sperren in iOS (Swift)

In iOS wird das standardmäßige NSLock seltener verwendet — Entwickler bevorzugen DispatchQueue mit Barrier-Flags oder os_unfair_lock. Swift 5.7+ bietet moderne Synchronisationsmechanismen durch Actors, die den Zustand automatisch schützen.

swift
import Foundation

actor DataStore {
    private var items: [String] = []

    func add(_ item: String) {
        items.append(item)
    }

    func getAll() -> [String] {
        items
    }
}

Empfehlungen zur Deadlock-Vermeidung

Um Deadlocks zu vermeiden, befolgen Sie eine einheitliche Sperrreihenfolge im gesamten Projekt. Verwenden Sie tryLock mit Timeout anstelle von lock(), wo immer längeres Blockieren möglich ist. Erwägen Sie die Verwendung von Lock-Free-Algorithmen (AtomicReference, ConcurrentHashMap) anstelle traditioneller Sperren.

Bewährte Praktiken für die Arbeit mit Lock

Die Verwendung von Lock erfordert Disziplin und die Einhaltung mehrerer Regeln, die Deadlocks und Leistungseinbußen verhindern. Diese Praktiken wurden von der Java-Community in 20 Jahren Nutzung des Pakets java.util.concurrent entwickelt.

Freigabe in finally

Das wichtigste Muster ist lock in finally. Unabhängig davon, ob der kritische Abschnitt erfolgreich abgeschlossen wird oder eine Ausnahme auslöst, muss die Sperre freigegeben werden. Dies stellt sicher, dass andere Threads aufgrund eines einzelnen Fehlers nicht für immer blockiert werden. In Kotlin wird dieses Muster elegant durch die withLock-Erweiterung gelöst.

Minimierung der Haltezeit

Der kritische Abschnitt sollte so kurz wie möglich sein. Führen Sie niemals E/A, Netzwerkanfragen oder lange Berechnungen innerhalb einer Sperre durch. Wenn Sie Daten von einem Server lesen müssen, holen Sie sie zuerst ab und erwerben Sie die Sperre nur zur Aktualisierung des gemeinsamen Zustands. Dies reduziert Konflikte und verbessert den Systemdurchsatz.

Einheitliche Sperrreihenfolge

Um Deadlocks bei der Arbeit mit mehreren Sperren zu verhindern, legen Sie eine globale Sperrreihenfolge für das gesamte Projekt fest. Wenn zuerst lockA und dann lockB erworben wird, muss jede umgekehrte Reihenfolge durch Code-Review-Regeln verboten werden. Verwenden Sie statische Analyzer wie SpotBugs und IntelliJ Inspections zur automatischen Überprüfung.

Häufig gestellte Fragen

Was ist der Unterschied zwischen Lock und synchronized?

Lock ist eine explizite Schnittstelle mit Timeout- und unterbrechbarem Wartesupport. synchronized erwirbt und gibt den Monitor automatisch frei, erlaubt aber nicht die Verwendung von tryLock, lockInterruptibly oder mehreren Conditions. Lock ist flexibler, erfordert aber manuelle Freigabe in finally.

Was ist eine faire Sperre (fair lock)?

Eine faire Sperre garantiert FIFO-Reihenfolge: Der Thread, der am längsten wartet, erhält zuerst die Sperre. Eine unfaire Sperre kann einem neuen Thread Zugriff vor wartenden Threads gewähren, was den Durchsatz erhöht, aber bei wartenden Threads zu Aushungerung führen kann.

Wie vermeide ich Deadlocks bei der Verwendung von Lock?

Befolgen Sie eine feste Reihenfolge für den Erwerb aller Sperren, verwenden Sie tryLock mit Timeout anstelle von bedingungslosem lock() und minimieren Sie die Anzahl gleichzeitig gehaltener Sperren. Die Verwendung von Lock-Free-Datenstrukturen reduziert ebenfalls das Deadlock-Risiko.

Was ist eine Condition bei Lock?

Condition ist das Analogon zu wait/notify für Lock und ermöglicht mehrere unabhängige Warteschlangen. Jeder newCondition()-Aufruf erstellt eine separate Warteschlange und bietet eine präzisere Kontrolle über die Thread-Aufweckung im Vergleich zur einzigen Warteschlange von synchronized.

Welche Sperre sollte ich für eine mobile Anwendung wählen?

Für Android mit Koroutinen verwenden Sie Mutex aus kotlinx.coroutines — es setzt die Koroutine aus, anstatt den Thread zu blockieren. Für iOS mit Swift 5.7+ sind Actors vorzuziehen, die den Zustandszugriff automatisch synchronisieren. Lassen Sie ReentrantLock für Legacy-Code und Low-Level-Szenarien.

Zusammenfassung

  • Lock ist eine explizite Sperrverwaltungsschnittstelle aus java.util.concurrent.locks.
  • ReentrantLock ist die Hauptimplementierung mit Unterstützung für Wiedererfassung und Fairness.
  • ReadWriteLock trennt Lese- und Schreibsperren für leseintensive Szenarien.
  • StampedLock fügt optimistisches Lesen für maximale Leistung hinzu.
  • Timeouts und Conditions sind die Hauptvorteile von Lock gegenüber synchronized.
  • Deadlock wird durch einheitliche Sperrreihenfolge und Verwendung von tryLock verhindert.
  • In der mobilen Entwicklung werden Koroutinen (Android) und Actors (iOS) empfohlen.

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.

Projekt besprechen

Lesen Sie auch