Doppelt überprüfte Sperrung

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche
Dieser Artikel oder nachfolgende Abschnitt ist nicht hinreichend mit Belegen (beispielsweise Einzelnachweisen) ausgestattet. Die fraglichen Angaben werden daher möglicherweise demnächst entfernt. Bitte hilf der Wikipedia, indem du die Angaben recherchierst und gute Belege einfügst. Näheres ist eventuell auf der Diskussionsseite oder in der Versionsgeschichte angegeben. Bitte entferne zuletzt diese Warnmarkierung.
Anleitung: Neutraler Standpunkt Die Neutralität dieses Artikels oder Abschnitts ist umstritten. Eine Begründung steht auf der Diskussionsseite. Weitere Informationen erhältst du hier.

Eine doppelt überprüfte Sperrung (englisch double-checked locking) ist ein Antimuster, welches auftritt, wenn eine doppelte Überprüfung durchgeführt wird, bevor eine Sperrung erfolgt. Dieses Muster wird häufig von unerfahrenen Programmierern genutzt, 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#:

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;
    }
  }
}

Einzelnachweise[Bearbeiten]

  1. Java Language Specification, Java SE 7 Edition: 12.4.1
  2. Java Language Specification, Java SE 7 Edition: 12.4.2