Singletons in Java

Singletons in Java

1. Einführung

In diesem kurzen Artikel werden die beiden beliebtesten Methoden zur Implementierung von Singletons in einfachem Java erläutert.

2. Klassenbasiertes Singleton

Am häufigsten wird ein Singleton implementiert, indem eine reguläre Klasse erstellt und Folgendes sichergestellt wird:

  • Ein privater Konstrukteur

  • Ein statisches Feld, das seine einzige Instanz enthält

  • Eine statische Factory-Methode zum Abrufen der Instanz

Wir werden auch eine Info-Eigenschaft hinzufügen, die nur zur späteren Verwendung verwendet wird. Unsere Implementierung sieht also so aus:

public final class ClassSingleton {

    private static ClassSingleton INSTANCE;
    private String info = "Initial info class";

    private ClassSingleton() {
    }

    public static ClassSingleton getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new ClassSingleton();
        }

        return INSTANCE;
    }

    // getters and setters
}

Obwohl dies ein gängiger Ansatz ist, ist es wichtig zu beachten, dass escan be problematic in multithreading scenarios ist, was der Hauptgrund für die Verwendung von Singletons ist.

Einfach ausgedrückt kann dies zu mehr als einer Instanz führen und das Kernprinzip des Musters verletzen. Obwohl es Sperrlösungen für dieses Problem gibt, löst unser nächster Ansatz diese Probleme auf Stammebene.

3. Enum Singleton

Lassen Sie uns in Zukunft keinen anderen interessanten Ansatz diskutieren - nämlich die Verwendung von Aufzählungen:

public enum EnumSingleton {

    INSTANCE("Initial class info");

    private String info;

    private EnumSingleton(String info) {
        this.info = info;
    }

    public EnumSingleton getInstance() {
        return INSTANCE;
    }

    // getters and setters
}

Bei diesem Ansatz wird die Serialisierung und Threadsicherheit durch die Enum-Implementierung selbst gewährleistet. Dadurch wird intern sichergestellt, dass nur die einzige Instanz verfügbar ist, und die in der klassenbasierten Implementierung genannten Probleme werden behoben.

4. Verwendungszweck

Um unsereClassSingleton zu verwenden, müssen wir die Instanz einfach statisch abrufen:

ClassSingleton classSingleton1 = ClassSingleton.getInstance();

System.out.println(classSingleton1.getInfo()); //Initial class info

ClassSingleton classSingleton2 = ClassSingleton.getInstance();
classSingleton2.setInfo("New class info");

System.out.println(classSingleton1.getInfo()); //New class info
System.out.println(classSingleton2.getInfo()); //New class info

DieEnumSingleton können wie jede andere Java-Enumeration verwendet werden:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();

System.out.println(enumSingleton1.getInfo()); //Initial enum info

EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New enum info");

System.out.println(enumSingleton1.getInfo()); // New enum info
System.out.println(enumSingleton2.getInfo()); // New enum info

5. Häufige Fehler

Singleton ist ein täuschend einfaches Entwurfsmuster, und es gibt nur wenige häufige Fehler, die ein Programmierer beim Erstellen eines Singletons begehen könnte.

Wir unterscheiden zwei Arten von Problemen mit Singletons:

  • existential (brauchen wir einen Singleton?)

  • Implementierung (setzen wir es richtig um?)

5.1. Existenzielle Probleme

Konzeptionell ist ein Singleton eine Art globale Variable. Im Allgemeinen wissen wir, dass globale Variablen vermieden werden sollten - insbesondere wenn ihre Zustände veränderlich sind.

Wir sagen nicht, dass wir niemals Singletons verwenden sollten. Wir sagen jedoch, dass es möglicherweise effizientere Möglichkeiten gibt, unseren Code zu organisieren.

Wenn die Implementierung einer Methode von einem Singleton-Objekt abhängt, warum nicht als Parameter übergeben? In diesem Fall zeigen wir ausdrücklich, wovon die Methode abhängt. Infolgedessen können wir diese Abhängigkeiten (falls erforderlich) bei der Durchführung von Tests leicht verspotten.

Beispielsweise werden Singletons häufig verwendet, um die Konfigurationsdaten der Anwendung zu erfassen (d. H. Die Verbindung zum Repository). Wenn sie als globale Objekte verwendet werden, wird es schwierig, die Konfiguration für die Testumgebung auszuwählen.

Wenn wir die Tests ausführen, wird die Produktionsdatenbank daher mit den Testdaten verderbt, was kaum akzeptabel ist.

Wenn wir einen Singleton benötigen, könnten wir die Möglichkeit in Betracht ziehen, seine Instanziierung an eine andere Klasse - eine Art Factory - zu delegieren, die sicherstellen sollte, dass nur eine Instanz des Singleton im Spiel ist.

5.2. Implementierungsprobleme

Obwohl die Singletons recht einfach zu sein scheinen, können ihre Implementierungen unter verschiedenen Problemen leiden. Alle führen dazu, dass wir möglicherweise mehr als nur eine Instanz der Klasse haben.

Synchronization Die oben vorgestellte Implementierung mit einem privaten Konstruktor ist nicht threadsicher: Sie funktioniert gut in einer Umgebung mit einem Thread, aber in einer Umgebung mit mehreren Threads sollten wir die Synchronisationstechnik verwenden, um die Atomizität von zu gewährleisten die Operation:

public synchronized static ClassSingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new ClassSingleton();
    }
    return INSTANCE;
}

Beachten Sie das Schlüsselwortsynchronized in der Methodendeklaration. Der Hauptteil der Methode verfügt über mehrere Operationen (Vergleich, Instanziierung und Rückgabe).

Ohne Synchronisation besteht die Möglichkeit, dass zwei Threads ihre Ausführungen so verschachteln, dass der AusdruckINSTANCE == null für beide Threads zutrue  und folglich zu zwei Instanzen vonClassSingleton ausgewertet wird erstellt werden.

Synchronization können die Leistung erheblich beeinträchtigen. Wenn dieser Code häufig aufgerufen wird, sollten wir ihn mit verschiedenen Techniken wielazy initialization oderdouble-checked locking beschleunigen (beachten Sie, dass dies aufgrund von Compiler-Optimierungen möglicherweise nicht wie erwartet funktioniert). Weitere Informationen finden Sie in unserem Tutorial „https://www.example.com/java-singleton-double-checked-locking[Double-Checked Locking with Singleton]“.

Multiple Instances Es gibt mehrere andere Probleme mit den Singletons im Zusammenhang mit JVM selbst, die dazu führen können, dass wir mehrere Instanzen eines Singletons haben. Diese Probleme sind sehr subtil und wir werden für jedes eine kurze Beschreibung geben:

  1. Ein Singleton soll pro JVM eindeutig sein. Dies kann ein Problem für verteilte Systeme oder Systeme sein, deren Interna auf verteilten Technologien basieren.

  2. Jeder Klassenlader lädt möglicherweise seine Version des Singleton.

  3. Ein Singleton kann durch Müll gesammelt werden, wenn niemand einen Verweis darauf hat. Dieses Problem führt nicht dazu, dass mehrere Singleton-Instanzen gleichzeitig vorhanden sind. Bei der Neuerstellung kann sich die Instanz jedoch von der vorherigen Version unterscheiden.

6. Fazit

In diesem kurzen Tutorial haben wir uns darauf konzentriert, wie das Singleton-Muster nur mit Java-Kern implementiert wird und wie sichergestellt wird, dass es konsistent ist und wie diese Implementierungen verwendet werden.

Die vollständige Implementierung dieser Beispiele finden Sie inover on GitHub.