Doppelt überprüfte Sperrung

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

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.

Doppelt überprüfte Sperrung in Java[Bearbeiten]

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 englisch lazy erzeugt werden soll:

public class Foo {
   private Helper helper = null; 
 
   public Helper getHelper() {
     if(helper == null) // erste Prüfung     synchronized(this) {
       if(helper == null) // zweite Prüfung         helper = new Helper();
     }
     return helper; 
   }
 
   // ...
}

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 Object zurück.

Lösung[Bearbeiten]

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.

public class Foo {
   private static class HelperHolder {
      public static Helper helper = new Helper();
   }
 
   public Helper getHelper() {
      return HelperHolder.helper; 
   }
 
   // ...

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

Doppelt überprüfte Sperrung in C#[Bearbeiten]

Analog zu Java existiert die doppelt geprüfte Sperrung auch in C#. Für eine korrekte Implementierung muss hierbei zwingend das volatile-Schlüsselwort eingesetzt werden.

class Singleton 
{
    private Singleton() { }
    private static volatile Singleton instance; 
    public static Singleton Instance 
    {
        get
        {
            if (instance == null)             {                lock(_lock)                {                      if (instance == null)                     {
                        instance = new Singleton();
                    }
                }   
            }
            return instance;
        }
    }
 
    // Hilfsfeld für eine sichere Threadsynchronisierung    private static object _lock = new object();}

Zwar existiert das synchronized-Schlüsselwort in C# nicht, es kann jedoch über das MethodImpl-Attribut dieselbe Funktion erzielt werden. Ein getrenntes Lock-Objekt wird nicht benötigt.

class Singleton 
{
    private Singleton() { }
    private static volatile Singleton instance;
 
    public static Singleton Instance 
    {
        [MethodImpl(MethodImplOptions.Synchronized)]        get
        {
            if (instance == null) 
            {
                instance = new Singleton();
            }
 
            return instance;
        }
    }
}

Eine bessere Möglichkeit ab .NET 4.0 ist der Einsatz der Lazy<T>-Klasse[3], welche intern eine korrekte Form der doppelt überprüften Sperrung verwendet.[4]

Einzelnachweise[Bearbeiten]

  1. Java Language Specification, Java SE 7 Edition: 12.4.1
  2. Java Language Specification, Java SE 7 Edition: 12.4.2
  3. Lazy<T> Class. In: MSDN. Microsoft, abgerufen am 27. Juli 2014 (englisch).
  4.  Ben Watson: Writing High-Performance .NET Code. 23. Juli 2014, ISBN 9780990583431.