Singleton-Muster
Das Singleton-Muster ist eines der einfachsten Entwurfsmuster in Java. Diese Art von Entwurfsmuster gehört zu den Erzeugungsmustern und bietet eine optimale Möglichkeit, Objekte zu erstellen.
Dieses Muster umfasst eine einzelne Klasse, die für die Erstellung ihres eigenen Objekts verantwortlich ist und dabei sicherstellt, dass nur ein einziges Objekt erstellt wird. Diese Klasse bietet eine Möglichkeit, direkt auf ihr eindeutiges Objekt zuzugreifen, ohne ein Objekt der Klasse instanziieren zu müssen.
Das Singleton-Muster ist ein Erzeugungs-Entwurfsmuster, das sicherstellt, dass eine Klasse nur eine Instanz hat, und bietet einen globalen Zugriffspunkt, um auf diese Instanz zuzugreifen.
Hinweis:
- 1. Eine Singleton-Klasse kann nur eine Instanz haben.
- 2. Die Singleton-Klasse muss ihre eigene eindeutige Instanz erstellen.
- 3. Die Singleton-Klasse muss diese Instanz allen anderen Objekten bereitstellen.
summary
Singleton-Muster
Absicht
Stellen Sie sicher, dass eine Klasse nur eine Instanz hat, und bieten Sie einen globalen Zugriffspunkt, um auf diese Instanz zuzugreifen.
Hauptsächlich gelöstes Problem
Das Problem, global verwendete Klasseninstanzen häufig zu erstellen und zu zerstören.
Wann verwenden
Wenn die Anzahl der Instanzen gesteuert und Systemressourcen eingespart werden müssen.
Lösung
Prüfen, ob das Singleton bereits im System existiert; wenn es existiert, diese Instanz zurückgeben; wenn es nicht existiert, eine neue Instanz erstellen.
Schlüsselcode
Der Konstruktor ist privat.
Anwendungsbeispiel
- Eine Klasse hat nur einen Klassenlehrer.
- Wenn Windows in einer Umgebung mit mehreren Prozessen und mehreren Threads auf Dateien zugreift, muss dies über eine eindeutige Instanz behandelt werden, um zu vermeiden, dass mehrere Prozesse oder Threads gleichzeitig auf dieselbe Datei zugreifen.
- Der Geräte-Manager ist als Singleton-Muster entworfen. Wenn ein Computer beispielsweise zwei Drucker hat, wird vermieden, dass dieselbe Datei gleichzeitig gedruckt wird.
Vorteile
- Es gibt nur eine Instanz im Speicher, was den Speicher-Overhead reduziert, insbesondere wenn Instanzen häufig erstellt und zerstört werden (z. B. bei der Verwaltung des Homepage-Seitencaches einer Management-Schule).
- Mehrfache Belegung von Ressourcen vermeiden (z. B. Dateischreibvorgänge).
Nachteile
- Keine Schnittstelle, kann nicht vererbt werden.
- Es steht im Konflikt mit dem Single-Responsibility-Prinzip; eine Klasse sollte sich nur um ihre interne Logik kümmern, nicht um die Art und Weise, wie sie instanziiert wird.
Anwendungsfall
- Eindeutige Seriennummern generieren.
- Ein Zähler in der WEB-Entwicklung, der vermeidet, dass der Zählerstand bei jeder Aktualisierung in der Datenbank erhöht wird; stattdessen wird er zuerst zwischengespeichert.
- Erstellen von Objekten, die zu viele Ressourcen verbrauchen, wie I/O und Datenbankverbindungen.
Hinweise
- Threadsicherheit:
getInstance()In der Methode müssen Synchronisationssperren verwendet werden.synchronized (Singleton.class)Um zu verhindern, dass mehrere Threads gleichzeitig eintreten und dadurch die Instanz mehrfach erstellt wird. - Lazy-Initialisierung: Die Instanz wird beim ersten Aufruf
getInstance()der Methode erstellt. - Serialisierung und Deserialisierung.: Überschreiben Sie die
readResolveMethode, um sicherzustellen, dass während der Deserialisierung keine neue Instanz erstellt wird. - Reflection-Angriff: Fügen Sie Schutzcode im Konstruktor hinzu, um zu verhindern, dass durch Reflection neue Instanzen erstellt werden.
- Class-Loader-Problem: Achten Sie auf die Probleme mit mehreren Instanzen, die durch komplexe Class-Loading-Umgebungen verursacht werden können.
Struktur
Das Singleton-Muster umfasst die folgenden Hauptrollen:
- Singleton-Klasse: eine Klasse, die die Singleton-Instanz enthält und üblicherweise den Konstruktor als privat deklariert.
- Statische Membervariable: eine statische Membervariable, die zum Speichern der Singleton-Instanz verwendet wird.
- Instanzabrufmethode: eine statische Methode, die zum Abrufen der Singleton-Instanz verwendet wird.
- Privater Konstruktor: verhindert, dass externer Code die Singleton-Klasse direkt instanziiert.
- Umgang mit Thread-Sicherheit: stellt sicher, dass die Erstellung der Singleton-Instanz in einer Multithread-Umgebung sicher ist.
Implementierung
Wir werden eineSingleObject-Klasse erstellen.SingleObjectDie Klasse hat ihren privaten Konstruktor und ihre eigene statische Instanz.
SingleObjectDie Klasse stellt eine statische Methode bereit, mit der die Außenwelt ihre statische Instanz abrufen kann.SingletonPatternDemoKlassenverwendungSingleObjectKlasse zum AbrufenSingleObjectObjekt.

Schritt 1
Eine Singleton-Klasse erstellen.
SingleObject.java
Schritt 2
Das einzige Objekt aus der Singleton-Klasse abrufen.
SingletonPatternDemo.java
Schritt 3
Programm ausführen, Ergebnis ausgeben:
Hello World!
Mehrere Implementierungsansätze für das Singleton-Muster
Es gibt mehrere Möglichkeiten, das Singleton-Muster zu implementieren, wie unten gezeigt:
1. Lazy-Modus, nicht threadsicher
Ob Lazy-Initialisierung:Ja
Ist es multithread-sicher:nein
Implementierungsschwierigkeit:einfach
Beschreibung:Dies ist die grundlegendste Implementierung. Das größte Problem bei dieser Implementierung ist, dass sie Multithreading nicht unterstützt. Da es keine synchronized-Sperre gibt, ist es streng genommen kein Singleton-Muster.
Diese Vorgehensweise weist ein offensichtliches Lazy-Loading auf, erfordert keine Threadsicherheit und kann im Multithreading nicht ordnungsgemäß funktionieren.
Beispiel
Die verschiedenen Implementierungsmethoden, die als Nächstes vorgestellt werden, unterstützen alle Multithreading, aber es gibt Unterschiede in der Leistung.
2. Lazy-Modus, threadsicher
Ob Lazy-Initialisierung:Ja
Ist es multithread-sicher:Ja
Implementierungsschwierigkeit:einfach
Beschreibung:Dieser Ansatz hat ein gutes Lazy Loading und kann unter Multithreading gut funktionieren, aber seine Effizienz ist sehr gering, und in 99% der Fälle ist keine Synchronisation erforderlich.
Vorteile: Erst beim ersten Aufruf initialisiert, wodurch Speicherverschwendung vermieden wird.
Nachteile: synchronized muss hinzugefügt werden, um das Singleton-Verhalten sicherzustellen, aber das Sperren beeinträchtigt die Effizienz.
Die Leistung von getInstance() ist für die Anwendung nicht kritisch (diese Methode wird nicht häufig verwendet).
Beispiel
3. Eager-Initialisierung
Ob Lazy-Initialisierung:nein
Ist es multithread-sicher:Ja
Implementierungsschwierigkeit:einfach
Beschreibung:Dieser Ansatz ist relativ verbreitet, aber er kann leicht Müllobjekte erzeugen.
Vorteile: keine Sperre, die Ausführungseffizienz wird verbessert.
Nachteile: wird beim Laden der Klasse initialisiert, wodurch Speicher verschwendet wird.
Es vermeidet Multithreading-Synchronisationsprobleme basierend auf dem Classloader-Mechanismus. Allerdings wird die Instanz instanziiert, wenn die Klasse geladen wird. Obwohl es viele Gründe gibt, die dazu führen, dass die Klasse geladen wird, sind es im Singleton-Muster meist Aufrufe der getInstance-Methode, aber es kann nicht bestätigt werden, dass es keine anderen Wege (oder andere statische Methoden) gibt, die dazu führen, dass die Klasse geladen wird. In diesem Fall erreicht die Initialisierung der Instanz offensichtlich nicht den Effekt des Lazy Loading.
Beispiel
4. Doppelt geprüfte Sperre / Double-Checked Locking (DCL, d. h. Double-Checked Locking)
JDK-Version:Seit JDK 1.5
Ob Lazy-Initialisierung:Ja
Ist es multithread-sicher:Ja
Implementierungsschwierigkeit:Komplexer
Beschreibung:Dieser Ansatz verwendet einen Doppelsperrmechanismus, der sicher ist und bei Multithreading eine hohe Leistung beibehält.
Die Leistung von getInstance() ist entscheidend für die Anwendung.
Beispiel
5. Registry/Statische innere Klasse
Ob Lazy-Initialisierung:Ja
Ist es multithread-sicher:Ja
Implementierungsschwierigkeit:Durchschnittlich
Beschreibung:Dieser Ansatz kann denselben Effekt wie Double-Checked Locking erzielen, ist aber einfacher zu implementieren. Für die verzögerte Initialisierung von statischen Feldern sollte dieser Ansatz anstelle von Double-Checked Locking verwendet werden. Dieser Ansatz gilt nur für statische Felder; Double-Checked Locking kann verwendet werden, wenn Instanzfelder eine verzögerte Initialisierung erfordern.
Dieser Ansatz verwendet ebenfalls den ClassLoader-Mechanismus, um sicherzustellen, dass nur ein Thread die Instanz initialisiert. Er unterscheidet sich von Ansatz 3 darin, dass bei Ansatz 3 die Instanz instanziiert wird, sobald die Singleton-Klasse geladen ist (ohne Lazy Loading zu erreichen), während bei diesem Ansatz die Instanz nach dem Laden der Singleton-Klasse nicht unbedingt initialisiert wird. Da die SingletonHolder-Klasse nicht aktiv verwendet wird, wird die SingletonHolder-Klasse erst dann explizit geladen, wenn die Methode getInstance explizit aufgerufen wird, wodurch die Instanz instanziiert wird. Stellen Sie sich vor, dass, wenn das Instanziieren der Instanz viele Ressourcen verbraucht, Sie möglicherweise möchten, dass sie verzögert geladen wird. Andererseits möchten Sie möglicherweise nicht, dass sie instanziiert wird, wenn die Singleton-Klasse geladen wird, da nicht garantiert ist, dass die Singleton-Klasse nicht anderswo aktiv verwendet und geladen wird. In diesem Fall ist das Instanziieren der Instanz zu diesem Zeitpunkt offensichtlich unangemessen. An dieser Stelle ist dieser Ansatz viel sinnvoller als Ansatz 3.
Beispiel
6. Enumeration
JDK-Version:Seit JDK 1.5
Ob Lazy-Initialisierung:nein
Ist es multithread-sicher:Ja
Implementierungsschwierigkeit:einfach
Beschreibung:Dieser Implementierungsansatz wurde nicht breit übernommen, ist aber die beste Methode zur Implementierung des Singleton-Musters. Er ist prägnanter, unterstützt automatisch den Serialisierungsmechanismus und verhindert absolut mehrfache Instanziierung.
Dieser Ansatz wird von Josh Bloch, dem Autor von Effective Java, befürwortet. Er vermeidet nicht nur Synchronisationsprobleme beim Multithreading, sondern unterstützt auch automatisch den Serialisierungsmechanismus, verhindert, dass durch Deserialisierung neue Objekte erneut erzeugt werden, und verhindert absolut mehrfache Instanziierung. Da die Enum-Funktion jedoch erst nach JDK 1.5 hinzugefügt wurde, fühlt sich das Schreiben auf diese Weise unweigerlich ungewohnt an, und in der Praxis wird es selten verwendet.
Es kann den privaten Konstruktor nicht durch einen Reflection-Angriff aufrufen.
Beispiel
Aus Erfahrung:Im Allgemeinen wird nicht empfohlen, die 1. und 2. Lazy-Ansätze zu verwenden; der 3. Eager-Ansatz wird empfohlen. Nur wenn Lazy Loading explizit implementiert werden muss, sollte der 5. Registrierungsansatz verwendet werden. Wenn die Deserialisierung an der Objekterstellung beteiligt ist, können Sie den 6. Enum-Ansatz ausprobieren. Wenn es andere spezielle Anforderungen gibt, können Sie den 4. Double-Checked-Locking-Ansatz in Betracht ziehen.
Weitere Erweiterungen