Doppelt überprüfte Sperrung
# Doppelt überprüfte Sperrung

Eine doppelt überprüfte Sperrung (englisch double-checked locking) ist ein Muster in der Softwareentwicklung, welches dazu dient, den Zugriff auf ein gemeinsames Objekt durch mehrere gleichzeitig laufende Threads zu regeln.

Eine falsch implementierte doppelt überprüfte Sperrung ist ein Antimuster. Dies passiert oft unerfahrenen Programmierern, die von der Problematik des Lockings wissen, aber die falschen Schlüsse ziehen.

## Thread sicherer Container

Im folgenden Beispiel wird ein Element einem Container hinzugefügt oder entfernt. Es wird ein Mutex verwendet damit die Funktionen von mehreren Threads nebenläufig ausgeführt werden können.

Der Mutex muss in jedem durchgang von entfernen gesperrt werden. Um das zu umgehen kann eine doppelt überprüfte Sperrung verwendet werden:

Falls der Container leer ist muss nur eine Variable synchronisiert gelesen werden. Dies ist schneller als ein Mutex zu sperren. Die erste Prüfung muss synchronisiert sein, da der Computer ansonsten annehmen kann, dass kein anderer Thread leer verändert und er einen teilweise geschriebener Wert lädt. Bei der zweiten Prüfung stellt der Mutex die Synchronisation sicher.

## Doppelt überprüfte Sperrung in Java

Obwohl mit Java 5 unter einer neuen Semantik des Schlüsselwortes volatile eine doppelt überprüfte Sperrung threadsicher realisiert werden kann, gilt es immer noch als Anti-Pattern, da es zu umständlich und ineffizient ist. Zudem ist der Effizienznachteil von volatile kaum kleiner als von synchronized.

Beispiel

Das folgende Beispiel zeigt die Problematik in der getHelper-Methode, in der für jedes Foo-Objekt genau ein Helper-Objekt beim ersten Zugriff erzeugt werden soll:

Die Schnittstelle Helper wird genutzt, um außerhalb eines Foo-Objektes auf dem Helper-Objekt arbeiten zu können. Definiert man wie hier helper nicht als volatile, ist die doppelte Prüfung problematisch, weil z. B. ein Java JIT-Compiler den Assemblercode so umsortieren kann, dass der Verweis auf das Helper-Objekt gesetzt wird, bevor der Konstruktor vom Helper-Objekt vollständig durchlaufen wurde. In diesem Fall liefert getHelper ein nicht initialisiertes Objekt zurück.

### Lösung

Ab Java 5 werden volatile definierte Variablen erst nach vollständiger Abarbeitung des Konstruktors sichtbar. Wird also die Variable helper als volatile definiert, läuft obiges Beispiel korrekt durch.

Falls – wie bei der Implementierung eines Singletons – nur eine einzige Instanz pro Klasse existieren soll, gibt es eine leicht zu implementierende Lösung: Das Attribut wird als static deklariert und die Erzeugung des Objekts in eine Unterklasse (hier: nested class) ausgegliedert – das sog. initialization on demand holder-Idiom.

Hierbei wird das statische Attribut der Klasse HelperHolder erst beim Aufruf durch getHelper instanziert, also „lazy“, und die Virtuelle Maschine sorgt für die Threadsicherheit.

## Doppelt überprüfte Sperrung in C#

Analog zu Java existiert die doppelt geprüfte Sperrung auch in C#.

Soll kein dezidiertes Hilfsfeld genutzt werden, kann, wie in Java, auf this gelockt werden. Dies gilt jedoch als bad-practice, da hierdurch leicht sog. Deadlocks entstehen können. Ein dezidiertes Lock-Objekt ist in so gut wie jedem Falle vorzuziehen.

Eine weitere Möglichkeit besteht darin, die Singleton-Variable direkt in der Felddeklaration zu initialisieren. Dadurch ist die überprüfte Sperrung überflüssig, jedoch ist die Initialisierung dann nicht mehr lazy, bzw. wird beim ersten Zugriff auf die Klasse durchgeführt. Threadsicherheit wird durch die CLR gewährt.

Die beste Möglichkeit ab .NET 4.0 ist der Einsatz der Lazy<T>-Klasse, welche intern eine korrekte Form der doppelt überprüften Sperrung verwendet.

