CodeGym/Java-Blog/Random-DE/Java-Singleton-Klasse
Autor
Artem Divertitto
Senior Android Developer at United Tech

Java-Singleton-Klasse

Veröffentlicht in der Gruppe Random-DE
Hallo! Heute werden wir uns mit den Details verschiedener Entwurfsmuster befassen, beginnend mit dem Java-Singleton-Muster. Lassen Sie uns noch einmal Revue passieren lassen: Was wissen wir über Designmuster im Allgemeinen? Entwurfsmuster sind Best Practices, die wir zur Lösung einer Reihe bekannter Probleme anwenden können. Entwurfsmuster sind im Allgemeinen an keine Programmiersprache gebunden. Betrachten Sie sie als eine Reihe von Empfehlungen, die Ihnen helfen, Fehler zu vermeiden und das Rad nicht neu zu erfinden.Entwurfsmuster: Singleton – 1

Was ist ein Singleton in Java?

Singleton ist eines der einfachsten Entwurfsmuster auf Klassenebene. Manchmal sagen die Leute „Diese Klasse ist Singleton“, was bedeutet, dass die Klasse das Singleton-Entwurfsmuster implementiert. Manchmal ist es notwendig, eine Klasse zu schreiben, bei der wir die Instanziierung auf ein einzelnes Objekt beschränken. Zum Beispiel eine Klasse, die für die Protokollierung oder die Verbindung zu einem verantwortlich ist Datenbank. Das Singleton-Entwurfsmuster beschreibt, wie wir dies erreichen können. Ein Singleton ist ein Entwurfsmuster, das zwei Dinge tut:
  1. Es garantiert, dass es immer nur eine Instanz der Klasse gibt.

  2. Es bietet einen einzigen globalen Zugriffspunkt auf diese Instanz.

Daher gibt es zwei Merkmale, die für nahezu jede Implementierung des Singleton-Musters charakteristisch sind:
  1. Ein privater Konstrukteur. Dies schränkt die Möglichkeit ein, Objekte der Klasse außerhalb der Klasse selbst zu erstellen.

  2. Eine öffentliche statische Methode, die die Instanz der Klasse zurückgibt. Diese Methode heißt getInstance . Dies ist der Punkt des globalen Zugriffs auf die Klasseninstanz.

Umsetzungsmöglichkeiten

Das Singleton-Entwurfsmuster wird auf verschiedene Arten angewendet. Jede Option ist auf ihre Art gut und schlecht. Wie immer gibt es hier keine perfekte Option, aber wir sollten eine anstreben. Lassen Sie uns zunächst entscheiden, was gut und schlecht ist und welche Metriken Einfluss darauf haben, wie wir die verschiedenen Implementierungen des Entwurfsmusters bewerten. Beginnen wir mit dem Guten. Hier sind Faktoren, die eine Implementierung saftiger und ansprechender machen:
  • Verzögerte Initialisierung: Die Instanz wird erst erstellt, wenn sie benötigt wird.

  • Einfacher und transparenter Code: Diese Metrik ist natürlich subjektiv, aber wichtig.

  • Thread-Sicherheit: Korrekter Betrieb in einer Multithread-Umgebung.

  • Hohe Leistung in einer Multithread-Umgebung: geringe oder keine Thread-Blockierung beim Teilen einer Ressource.

Jetzt die Nachteile. Wir listen Faktoren auf, die eine Implementierung in ein schlechtes Licht rücken:
  • Keine verzögerte Initialisierung: Wenn die Klasse geladen wird, wenn die Anwendung gestartet wird, unabhängig davon, ob sie benötigt wird oder nicht (paradoxerweise ist es in der IT-Welt besser, faul zu sein)

  • Komplexer und schwer lesbarer Code. Auch diese Metrik ist subjektiv. Wenn Ihre Augen anfangen zu bluten, gehen wir davon aus, dass die Umsetzung nicht die beste ist.

  • Mangelnde Thread-Sicherheit. Mit anderen Worten: „Thread-Gefahr“. Falscher Vorgang in einer Multithread-Umgebung.

  • Schlechte Leistung in einer Multithread-Umgebung: 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 aufzuzeigen:

Einfach

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
Die einfachste Implementierung. Vorteile:
  • Einfacher und transparenter Code

  • Thread-Sicherheit

  • Hohe Leistung in einer Multithread-Umgebung

Nachteile:
  • Keine verzögerte Initialisierung.
In einem Versuch, den vorherigen Mangel zu beheben, erhalten wir Implementierung Nummer zwei:

Verzögerte Initialisierung

public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Vorteile:
  • Verzögerte Initialisierung.

Nachteile:
  • Nicht Thread-sicher

Diese Implementierung ist interessant. Wir können träge initialisieren, aber wir haben die Thread-Sicherheit verloren. Keine Sorge – wir synchronisieren alles in Implementierung Nummer drei.

Synchronisierter Zugriff

public class Singleton {
  private static final 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 Multithread-Leistung

Exzellent! In Implementierung Nummer drei stellen wir die Thread-Sicherheit wieder her! Natürlich ist es langsam ... Jetzt ist die getInstance- Methode synchronisiert, sodass sie jeweils nur von einem Thread ausgeführt werden kann. Anstatt die gesamte Methode zu synchronisieren, müssen wir tatsächlich nur den Teil davon synchronisieren, der die neue Instanz initialisiert. Aber wir können nicht einfach einen synchronisierten Block verwenden, um den Teil zu umschließen, der für die Erstellung der neuen Instanz verantwortlich ist. Dies würde die Thread-Sicherheit nicht gewährleisten. Es ist alles etwas komplizierter. Die ordnungsgemäße Synchronisierung ist unten zu sehen:

Doppelt überprüfte Verriegelung

public class Singleton {
    private static final 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 in früheren Java-Versionen unter 1.5 nicht unterstützt (die Verwendung des Schlüsselworts volatile ist seit der Version 1.5 behoben)

Beachten Sie, dass eine von zwei Bedingungen erfüllt sein muss, damit diese Implementierungsoption ordnungsgemäß funktioniert. Die INSTANCE- Variable muss entweder final oder volatile sein . Die letzte Implementierung, die wir heute besprechen werden, ist der Klassenhalter-Singleton .

Klasseninhaber

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 den korrekten Betrieb ist eine Garantie erforderlich, dass das Singleton- Objekt fehlerfrei initialisiert wird. Andernfalls führt der erste Aufruf der Methode getInstance zu einem ExceptionInInitializerError und alle nachfolgenden Aufrufe führen zu einem NoClassDefFoundError .

Diese Umsetzung ist nahezu perfekt. Es ist faul, threadsicher und schnell. Aber es gibt eine Nuance, wie in der Liste der Nachteile erläutert. Vergleich verschiedener Implementierungen des Singleton-Musters:
Implementierung Verzögerte Initialisierung Thread-Sicherheit Multithread-Leistung Wann verwenden?
Einfach - + Schnell Niemals. Oder möglicherweise, wenn eine verzögerte Initialisierung nicht wichtig ist. Aber nie wäre es besser.
Verzögerte Initialisierung + - Unzutreffend Immer wenn Multithreading nicht benötigt wird
Synchronisierter Zugriff + + Langsam Niemals. Oder möglicherweise, wenn die Multithread-Leistung keine Rolle spielt. Aber nie wäre es besser.
Doppelt überprüfte Verriegelung + + Schnell In seltenen Fällen, wenn Sie beim Erstellen des Singletons Ausnahmen behandeln müssen (wenn der Klassenhalter-Singleton nicht anwendbar ist)
Klasseninhaber + + Schnell Immer wenn Multithreading erforderlich ist und eine Garantie dafür besteht, dass das Singleton-Objekt problemlos erstellt wird.

Vor- und Nachteile des Singleton-Musters

Im Allgemeinen macht ein Singleton genau das, was von ihm erwartet wird:
  1. Es garantiert, dass es immer nur eine Instanz der Klasse gibt.

  2. Es bietet einen einzigen globalen Zugriffspunkt auf diese Instanz.

Dieses Muster weist jedoch Mängel auf:
  1. Ein Singleton verstößt gegen das Single-Responsibility-Prinzip: Zusätzlich zu seinen direkten Aufgaben kontrolliert die Singleton-Klasse auch die Anzahl der Instanzen.

  2. Die Abhängigkeit einer gewöhnlichen Klasse von einem Singleton ist im öffentlichen Vertrag der Klasse nicht sichtbar.

  3. Globale Variablen sind schlecht. Letztendlich wird aus einem Singleton eine umfangreiche globale Variable.

  4. Das Vorhandensein eines Singletons verringert die Testbarkeit der Anwendung als Ganzes und insbesondere der Klassen, die den Singleton verwenden.

Und das ist es! :) Wir haben mit Ihnen die Java Singleton-Klasse erkundet. Wenn Sie sich nun mit Ihren Programmierfreunden unterhalten, können Sie für den Rest Ihres Lebens nicht nur erwähnen, wie gut das Muster ist, sondern auch ein paar Worte darüber, was es schlecht macht. Viel Glück beim Erlernen dieses neuen Wissens.

Zusätzliche Lektüre:

Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare