Was ist ein Singleton?
Ein Singleton ist eines der einfachsten Entwurfsmuster, die auf eine Klasse angewendet werden können. Manchmal sagt man: „Diese Klasse ist ein Singleton“, was bedeutet, dass diese Klasse das Singleton-Entwurfsmuster implementiert. Manchmal ist es notwendig, eine Klasse zu schreiben, für die nur ein Objekt erstellt werden kann. Beispielsweise eine Klasse, die für die Protokollierung oder die Verbindung zu einer Datenbank verantwortlich ist. Das Singleton-Entwurfsmuster beschreibt, wie wir eine solche Aufgabe erfüllen können. Ein Singleton ist ein Entwurfsmuster, das zwei Dinge tut:-
Bietet eine Garantie dafür, dass eine Klasse nur eine Instanz der Klasse hat.
-
Stellt einen globalen Zugriffspunkt auf eine Instanz dieser Klasse bereit.
-
Privater Konstrukteur. Beschränkt die Möglichkeit, Klassenobjekte außerhalb der Klasse selbst zu erstellen.
-
Eine öffentliche statische Methode, die eine Instanz der Klasse zurückgibt. Diese Methode heißt
getInstance
. Dies ist der globale Zugriffspunkt zur Klasseninstanz.
Umsetzungsmöglichkeiten
Das Singleton-Entwurfsmuster wird auf unterschiedliche Weise verwendet. Jede Option ist auf ihre Art gut und schlecht. Auch hier gilt wie immer: Es gibt kein Ideal, aber man muss danach streben. Aber zunächst einmal definieren wir, was gut und was schlecht ist und welche Metriken die Bewertung der Umsetzung eines Entwurfsmusters beeinflussen. Beginnen wir mit dem Positiven. Hier sind die Kriterien, die der Umsetzung Saftigkeit und Attraktivität verleihen:-
Verzögerte Initialisierung: Wenn eine Klasse geladen wird, während die Anwendung genau dann ausgeführt wird, wenn sie benötigt wird.
-
Einfachheit und Transparenz des Codes: Die Metrik ist natürlich subjektiv, aber wichtig.
-
Thread-Sicherheit: Funktioniert ordnungsgemäß in einer Multithread-Umgebung.
-
Hohe Leistung in einer Multithread-Umgebung: Threads blockieren sich gegenseitig nur minimal oder gar nicht, wenn sie eine Ressource gemeinsam nutzen.
-
Nicht-verzögerte Initialisierung: Wenn eine Klasse geladen wird, wenn die Anwendung gestartet wird, unabhängig davon, ob sie benötigt wird oder nicht (ein Paradoxon, in der IT-Welt ist es besser, faul zu sein)
-
Komplexität und schlechte Lesbarkeit des Codes. Die Metrik ist auch subjektiv. Wir gehen davon aus, dass die Umsetzung mittelmäßig ist, wenn Blut aus den Augen kommt.
-
Mangelnde Thread-Sicherheit. Mit anderen Worten: „Thread-Gefahr“. Falscher Vorgang in einer Multithread-Umgebung.
-
Schlechte Leistung in einer Umgebung mit mehreren Threads: Threads blockieren sich ständig oder häufig gegenseitig, wenn sie eine Ressource gemeinsam nutzen.
Code
Jetzt sind wir bereit, verschiedene Implementierungsoptionen zu prüfen und die Vor- und Nachteile aufzulisten:Einfache Lösung
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Die einfachste Implementierung. Vorteile:
-
Einfachheit und Transparenz des Codes
-
Thread-Sicherheit
-
Hohe Leistung in einer Multithread-Umgebung
- Keine verzögerte Initialisierung.
Verzögerte Initialisierung
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Vorteile:
-
Verzögerte Initialisierung.
-
Nicht Thread-sicher
Synchronisierter Accessor
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Vorteile:
-
Verzögerte Initialisierung.
-
Thread-Sicherheit
-
Schlechte Leistung in einer Multithread-Umgebung
getInstance
ist die Methode synchronisiert und Sie können sie nur einzeln eingeben. Tatsächlich müssen wir nicht die gesamte Methode synchronisieren, sondern nur den Teil davon, in dem wir ein neues Klassenobjekt initialisieren. Aber wir können den Teil, der für die Erstellung eines neuen Objekts verantwortlich ist, nicht einfach synchronized
in einen Block einschließen: Dies bietet keine Thread-Sicherheit. Es ist etwas komplizierter. Die richtige Synchronisierungsmethode ist unten angegeben:
Doppelt geprüfte Verriegelung
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Vorteile:
-
Verzögerte Initialisierung.
-
Thread-Sicherheit
-
Hohe Leistung in einer Multithread-Umgebung
-
Wird auf Java-Versionen vor 1.5 nicht unterstützt (das Schlüsselwort volatile wurde in Version 1.5 behoben)
INSTANCE
muss entweder final
, oder sein volatile
. Die letzte Implementierung, die wir heute besprechen werden, ist Class Holder Singleton
.
Klassenhalter Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Vorteile:
-
Verzögerte Initialisierung.
-
Thread-Sicherheit.
-
Hohe Leistung in einer Multithread-Umgebung.
-
Für einen korrekten Betrieb muss sichergestellt werden, dass das Klassenobjekt
Singleton
fehlerfrei initialisiert wird.getInstance
Andernfalls endet der erste Methodenaufruf mit einem FehlerExceptionInInitializerError
und alle folgenden schlagen fehlNoClassDefFoundError
.
Implementierung | Verzögerte Initialisierung | Thread-Sicherheit | Multithreading-Geschwindigkeit | Wann verwenden? |
---|---|---|---|---|
Einfache Lösung | - | + | Schnell | Niemals. Oder wenn eine verzögerte Initialisierung nicht wichtig ist. Aber nie besser. |
Verzögerte Initialisierung | + | - | Unzutreffend | Immer wenn Multithreading nicht benötigt wird |
Synchronisierter Accessor | + | + | Langsam | Niemals. Oder wenn die Arbeitsgeschwindigkeit beim Multithreading keine Rolle spielt. Aber nie besser |
Doppelt geprüfte Verriegelung | + | + | Schnell | In seltenen Fällen, wenn Sie beim Erstellen eines Singletons Ausnahmen behandeln müssen. (wenn der Klasseninhaber Singleton nicht anwendbar ist) |
Klassenhalter Singleton | + | + | Schnell | Immer dann, wenn Multithreading erforderlich ist und eine Garantie dafür besteht, dass ein Singleton-Klassenobjekt problemlos erstellt wird. |
Vor- und Nachteile des Singleton-Musters
Im Allgemeinen macht der Singleton genau das, was von ihm erwartet wird:-
Bietet eine Garantie dafür, dass eine Klasse nur eine Instanz der Klasse hat.
-
Stellt einen globalen Zugriffspunkt auf eine Instanz dieser Klasse bereit.
-
Singleton verstößt gegen SRP (Single Responsibility Principle) – die Singleton-Klasse kontrolliert zusätzlich zu ihren unmittelbaren Verantwortlichkeiten auch die Anzahl ihrer Kopien.
-
Die Abhängigkeit einer regulären Klasse oder Methode von einem Singleton ist im öffentlichen Vertrag der Klasse nicht sichtbar.
-
Globale Variablen sind schlecht. Der Singleton verwandelt sich schließlich in eine große globale Variable.
-
Das Vorhandensein eines Singletons verringert die Testbarkeit der Anwendung im Allgemeinen und der Klassen, die den Singleton im Besonderen verwenden.
GO TO FULL VERSION