Singletons in Java

1. Einleitung

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

2. Klassenbasiertes Singleton

Der beliebteste Ansatz besteht darin, einen Singleton zu implementieren, indem eine reguläre Klasse erstellt wird und sichergestellt wird, dass:

  • 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 für die spätere Verwendung bestimmt ist. Unsere Implementierung sieht also folgendermaßen 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 }

Dies ist zwar ein gängiger Ansatz, es ist jedoch wichtig zu beachten, dass er in Multithreading-Szenarien problematisch sein kann. Dies ist der Hauptgrund für die Verwendung von Singletons.

Einfach ausgedrückt kann dies zu mehr als einer Instanz führen und das Kernprinzip des Musters brechen. Obwohl es für dieses Problem Sperrlösungen 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 Thread-Sicherheit durch die Enum-Implementierung selbst garantiert. Dadurch wird intern sichergestellt, dass nur die einzelne Instanz verfügbar ist, und die in der klassenbasierten Implementierung genannten Probleme werden behoben.

4. Verwendung

Um unser ClassSingleton 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

Wie für die EnumSingleton , können wir es wie jedes andere Java Enum verwenden:

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 Fallstricke

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

Wir unterscheiden zwei Arten von Problemen mit Singletons:

  • existenziell (brauchen wir einen Singleton?)
  • Implementierung (implementieren wir es richtig?)

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 explizit, 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 (dh 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 verwöhnt, was kaum akzeptabel ist.

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

5.2. Implementierungsprobleme

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

Synchronisation

Die Implementierung mit einem privaten Konstruktor, die wir oben vorgestellt haben, 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 der Operation zu gewährleisten:

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

Beachten Sie das in der Methodendeklaration synchronisierte Schlüsselwort . 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 Ausdruck INSTANCE == null für beide Threads als wahr ausgewertet wird und infolgedessen zwei Instanzen von ClassSingleton erstellt werden.

Die Synchronisierung kann die Leistung erheblich beeinträchtigen. Wenn dieser Code häufig aufgerufen wird, sollten wir ihn mithilfe verschiedener Techniken wie verzögerter Initialisierung oder doppelt überprüfter Sperrung beschleunigen (beachten Sie, dass dies aufgrund von Compiler-Optimierungen möglicherweise nicht wie erwartet funktioniert). Weitere Details finden Sie in unserem Tutorial „Double-Checked Locking with Singleton“.

Mehrere Instanzen

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 kann seine Version des Singletons laden.
  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 auf GitHub.