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 AufrufgetInstance()der Methode erstellt.
  • Serialisierung und Deserialisierung.: Überschreiben Sie diereadResolveMethode, 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.

单例模式的 UML 图

Schritt 1

Eine Singleton-Klasse erstellen.

SingleObject.java

public class SingleObject { //Erstellen eines Objekts von SingleObject private static SingleObject instance = new SingleObject(); //Den Konstruktor privat machen, damit die Klasse nicht instanziiert werden kann private SingleObject(){} //Das einzige verfügbare Objekt abrufen public static SingleObject getInstance(){ return instance; } public void showMessage(){ System.out.println("Hello World!"); } }

Schritt 2

Das einzige Objekt aus der Singleton-Klasse abrufen.

SingletonPatternDemo.java

public class SingletonPatternDemo { public static void main(String[] args) { //Ungültiger Konstruktor //Kompilierzeitfehler: Konstruktor SingleObject() ist nicht sichtbar //SingleObject object = new SingleObject(); //Das einzige verfügbare Objekt abrufen SingleObject object = SingleObject.getInstance(); //Nachricht anzeigen object.showMessage(); } }

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

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

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

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

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

public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }

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

public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }

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

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

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

public enum Singleton { INSTANCE; public void whateverMethod() { } }

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