Doppelte Überprüfung mit Singleton

Double-Checked Locking mit Singleton

1. Einführung

In diesem Tutorial werden wir uns mit dem doppelt überprüften Muster für das Verriegelungsdesign befassen. Dieses Muster reduziert die Anzahl der Erfassungen von Sperren, indem einfach der Sperrzustand im Voraus überprüft wird. Infolgedessen kommt es normalerweise zu einer Leistungssteigerung.

Schauen wir uns genauer an, wie es funktioniert.

2. Implementierung

Betrachten wir zunächst einen einfachen Singleton mit drakonischer Synchronisation:

public class DraconianSingleton {
    private static DraconianSingleton instance;
    public static synchronized DraconianSingleton getInstance() {
        if (instance == null) {
            instance = new DraconianSingleton();
        }
        return instance;
    }

    // private constructor and other methods ...
}

Obwohl diese Klasse threadsicher ist, können wir feststellen, dass es einen deutlichen Leistungsnachteil gibt: Jedes Mal, wenn wir die Instanz unseres Singletons erhalten möchten, müssen wir eine möglicherweise unnötige Sperre erwerben.

Um dies zu beheben,we could instead start by verifying if we need to create the object in the first place and only in that case we would acquire the lock.

Um die Operation atomar zu halten, wollen wir die gleiche Prüfung noch einmal durchführen, sobald wir den synchronisierten Block betreten:

public class DclSingleton {
    private static volatile DclSingleton instance;
    public static DclSingleton getInstance() {
        if (instance == null) {
            synchronized (DclSingleton .class) {
                if (instance == null) {
                    instance = new DclSingleton();
                }
            }
        }
        return instance;
    }

    // private constructor and other methods...
}

Eine Sache, die Sie bei diesem Muster beachten sollten, ist, dassthe field needs to be volatile ist, um Probleme mit der Cache-Inkohärenz zu vermeiden. Tatsächlich ermöglicht das Java-Speichermodell die Veröffentlichung von teilweise initialisierten Objekten, was wiederum zu subtilen Fehlern führen kann.

3. Alternativen

Obwohl das doppelt überprüfte Sperren möglicherweise die Dinge beschleunigen kann, gibt es mindestens zwei Probleme:

  • Da das Schlüsselwortvolatileordnungsgemäß funktioniert, ist es nicht mit Java 1.4 und niedrigeren Versionen kompatibel

  • Es ist ziemlich ausführlich und macht es schwierig, den Code zu lesen

Lassen Sie uns aus diesen Gründen einige andere Optionen ohne diese Mängel untersuchen. Alle folgenden Methoden delegieren die Synchronisierungstask an die JVM.

3.1. Frühe Initialisierung

Am einfachsten erreichen Sie die Threadsicherheit, indem Sie die Objekterstellung inline oder einen entsprechenden statischen Block verwenden. Dies nutzt die Tatsache aus, dass statische Felder und Blöcke nacheinander initialisiert werden (Java Language Specification 12.4.2):

public class EarlyInitSingleton {
    private static final EarlyInitSingleton INSTANCE = new EarlyInitSingleton();
    public static EarlyInitSingleton getInstance() {
        return INSTANCE;
    }

     // private constructor and other methods...
}

3.2. Initialisierung bei Bedarf

Da wir aus der Referenz zur Java-Sprachspezifikation im vorherigen Absatz wissen, dass eine Klasseninitialisierung auftritt, wenn wir zum ersten Mal eine ihrer Methoden oder Felder verwenden, können wir eine verschachtelte statische Klasse verwenden, um eine verzögerte Initialisierung zu implementieren:

public class InitOnDemandSingleton {
    private static class InstanceHolder {
        private static final InitOnDemandSingleton INSTANCE = new InitOnDemandSingleton();
    }
    public static InitOnDemandSingleton getInstance() {
        return InstanceHolder.INSTANCE;
    }

     // private constructor and other methods...
}

In diesem Fall weist die KlasseInstanceHolder das Feld zu, wenn wir zum ersten Mal darauf zugreifen, indem siegetInstance. aufruft

3.3. Enum Singleton

Die letzte Lösung stammt aus dem BuchEffective Java(Punkt 3) von Joshua Block und verwendet einenum anstelle einesclass. Zum Zeitpunkt des Schreibens wird dies als die prägnanteste und sicherste Methode zum Schreiben eines Singleton angesehen:

public enum EnumSingleton {
    INSTANCE;

    // other methods...
}

4. Fazit

Zusammenfassend ging dieser kurze Artikel das doppelt überprüfte Sperrmuster, seine Grenzen und einige Alternativen durch.

In der Praxis ist dieses Muster aufgrund der übermäßigen Ausführlichkeit und der mangelnden Abwärtskompatibilität fehleranfällig und sollte daher vermieden werden. Stattdessen sollten wir eine Alternative verwenden, mit der die JVM die Synchronisierung vornimmt.

Wie immer lautet der Code aller Beispieleavailable on GitHub.