JavaRush /Java-Blog /Random-DE /Designmuster: Singleton

Designmuster: Singleton

Veröffentlicht in der Gruppe Random-DE
Hallo! Heute werfen wir einen genaueren Blick auf verschiedene Designmuster und beginnen mit dem Singleton-Muster, das auch „Singleton“ genannt wird. Designmuster: Singleton – 1Erinnern wir uns: Was wissen wir über Designmuster im Allgemeinen? Entwurfsmuster sind Best Practices, die zur Lösung einer Reihe bekannter Probleme befolgt werden können. Entwurfsmuster sind im Allgemeinen an keine Programmiersprache gebunden. Betrachten Sie sie als eine Reihe von Empfehlungen, mit denen Sie Fehler vermeiden und das Rad nicht neu erfinden können.

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:
  1. Bietet eine Garantie dafür, dass eine Klasse nur eine Instanz der Klasse hat.

  2. Stellt einen globalen Zugriffspunkt auf eine Instanz dieser Klasse bereit.

Daher gibt es zwei Merkmale, die für fast jede Implementierung des Singleton-Musters charakteristisch sind:
  1. Privater Konstrukteur. Beschränkt die Möglichkeit, Klassenobjekte außerhalb der Klasse selbst zu erstellen.

  2. 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.

Jetzt die Nachteile. Lassen Sie uns die Kriterien auflisten, die die Umsetzung in einem schlechten Licht erscheinen lassen:
  • 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

Nachteile:
  • Keine verzögerte Initialisierung.
Um den letzten Fehler zu beheben, erhalten wir Implementierung Nummer zwei:

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.

Nachteile:
  • Nicht Thread-sicher

Die Umsetzung ist interessant. Wir können träge initialisieren, aber wir haben die Thread-Sicherheit verloren. Kein Problem: In Implementierung Nummer drei synchronisieren wir alles.

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

Nachteile:
  • Schlechte Leistung in einer Multithread-Umgebung

Großartig! In Implementierung Nummer drei haben wir die Thread-Sicherheit zurückgebracht! Stimmt, es ist langsam ... Jetzt getInstanceist 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 synchronizedin 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

Nachteile:
  • Wird auf Java-Versionen vor 1.5 nicht unterstützt (das Schlüsselwort volatile wurde in Version 1.5 behoben)

Ich stelle fest, dass eine von zwei Bedingungen erforderlich ist, damit diese Implementierungsoption ordnungsgemäß funktioniert. Die Variable INSTANCEmuss 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.

Nachteile:
  • Für einen korrekten Betrieb muss sichergestellt werden, dass das Klassenobjekt Singletonfehlerfrei initialisiert wird. getInstanceAndernfalls endet der erste Methodenaufruf mit einem Fehler ExceptionInInitializerErrorund alle folgenden schlagen fehl NoClassDefFoundError.

Die Umsetzung ist nahezu perfekt. Und faul und threadsicher und schnell. Aber es gibt eine Nuance, die im Minus beschrieben wird. Vergleichstabelle verschiedener Implementierungen des Singleton-Musters:
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:
  1. Bietet eine Garantie dafür, dass eine Klasse nur eine Instanz der Klasse hat.

  2. Stellt einen globalen Zugriffspunkt auf eine Instanz dieser Klasse bereit.

Dieses Muster hat jedoch Nachteile:
  1. Singleton verstößt gegen SRP (Single Responsibility Principle) – die Singleton-Klasse kontrolliert zusätzlich zu ihren unmittelbaren Verantwortlichkeiten auch die Anzahl ihrer Kopien.

  2. Die Abhängigkeit einer regulären Klasse oder Methode von einem Singleton ist im öffentlichen Vertrag der Klasse nicht sichtbar.

  3. Globale Variablen sind schlecht. Der Singleton verwandelt sich schließlich in eine große globale Variable.

  4. Das Vorhandensein eines Singletons verringert die Testbarkeit der Anwendung im Allgemeinen und der Klassen, die den Singleton im Besonderen verwenden.

OK, jetzt ist alles vorbei. Wir haben uns das Singleton-Entwurfsmuster angesehen. Jetzt können Sie in einem lebenslangen Gespräch mit Ihren Programmierfreunden nicht nur sagen, was daran gut ist, sondern auch ein paar Worte darüber, was daran schlecht ist. Viel Glück beim Erlernen neuen Wissens.

Zusätzliche Lektüre:

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION