Jeder kennt das Singleton -Modell und sie alle wissen, warum es faul, hungrig usw. ist, aber haben Sie ein gründliches Verständnis des Singleton -Musters? Heute werde ich Sie mitnehmen, um die Singletons in meinen Augen zu sehen, die sich von Ihrem Verständnis unterscheiden können.
Hier ist ein einfaches kleines Beispiel:
// Einfache faule öffentliche Klasse Singleton {// Singleton -Instanz variable private statische Singleton Instance = null; // private Konstruktionsmethode, um sicherzustellen, dass externe Klassen nicht über Constructor private Singleton () {} // Singleton Instance Public static Singleton getInstance () {if (instance == null) {Instance = new Singleton () erhalten können; } System.out.println ("Ich bin ein einfacher fauler Singleton!"); Rückkehrinstanz; }}Es ist leicht zu erkennen, dass der obige Code bei Multi-Threading unsicher ist. Wenn zwei Threads eingeben, wenn (Instanz == NULL) beurteilen, beurteilen beide Threads, dass die Instanz leer ist und dann zwei Instanzen erhalten werden. Dies ist nicht der Singleton, den wir wollen.
Als nächstes verwenden wir Sperren, um gegenseitige Ausschlüsse zu erreichen, um die Implementierung von Singletons sicherzustellen.
// Synchrone Methode Lazy Public Class Singleton {// Singleton -Instanz variable private statische Singleton Instance = null; // Private Konstruktionsmethode, um sicherzustellen, dass externe Klassen nicht über Constructor Private Singleton () {} // Singleton -Instanz öffentlich statische synchronisierte Singleton getInstance () {if (Instance == null) {Instance = New Singleton () erhalten können; } System.out.println ("Ich bin eine synchrone Methode Lazy Singleton!"); Rückkehrinstanz; }}Durch das Hinzufügen von Synchronisierten sorgt die Sicherheit der Gewinde, aber ist dies der beste Weg? Natürlich ist es nicht, denn auf diese Weise werden wir jedes Mal, wenn wir GetInstance () -Methode anrufen, gesperrt und müssen sie nur sperren, wenn wir GetInstance () beim ersten Mal anrufen. Dies wirkt sich offensichtlich auf die Leistung unseres Programms aus. Wir finden weiterhin bessere Wege.
Nach der Analyse wurde festgestellt, dass nur durch Sicherstellen, dass Instance = new Singleton () Thread -gegenseitiger Ausschluss ist, die Gewindesicherheit sichergestellt werden kann, sodass die folgende Version verfügbar ist:
// Double Lock Lazy Public Class Singleton {// Singleton Instance Variable Private statische Singleton Instance = null; // private Konstruktionsmethode, um sicherzustellen, dass externe Klassen nicht über Constructor private Singleton () {} // Singleton Instance Public static Singleton getInstance () {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {Instance = newchron () nicht instanziiert werden können. }}} System.out.println ("Ich bin ein doppeltes Lock -Lazy Singleton!"); Rückkehrinstanz; }} Diesmal scheint es nicht nur das Problem der Gewindesicherheit zu löst, sondern löst auch nicht jedes Mal, wenn Sie GetInstance () aufrufen, das Sperren hinzu, was zu einer Leistungsverschlechterung führt. Es sieht nach einer perfekten Lösung aus, ist es tatsächlich so?
Leider ist die Tatsache nicht so perfekt, wie wir dachten. Es gibt einen Mechanismus namens "Out-of-Order Writes" im Java-Plattform-Speichermodell. Es ist dieser Mechanismus, der das Versagen der Doppelprüfung der Verriegelungsmethode verursacht. Der Schlüssel zu diesem Problem liegt in Zeile 5 im obigen Code: Instance = new Singleton (); Diese Zeile macht tatsächlich zwei Dinge: 1. Rufen Sie den Konstruktor an und erstellen Sie eine Instanz. 2. Weisen Sie diese Instanz der Instanzvariableninstanz zu. Das Problem ist jedoch, dass diese beiden Schritte von JVM die Bestellung nicht garantieren. Das heißt. Es kann sein, dass die Instanz vor dem Aufrufen des Konstruktors auf nicht leeres Einsatz gesetzt wurde. Lassen Sie es uns gemeinsam analysieren:
Angenommen, es gibt zwei Fäden A und B
1. Faden A GEBEN DIE GETINSTANCE () -Methode.
2. Da die Instanz zu diesem Zeitpunkt leer ist, tritt ein Faden in den synchronisierten Block ein.
3.. Thread a executes instance = new Singleton (); Legt die Instanzvariableninstanz auf Nicht-Leerzeichen fest. (Beachten Sie, dass es vor dem Aufrufen des Konstruktors ist.)
4. Faden A exitiert und Faden B treten ein.
5. Thread B prüft, ob die Instanz leer ist und zu diesem Zeitpunkt nicht leer ist (im dritten Schritt wird sie mit Thread A nicht auf nicht leer eingestellt). Thread B gibt einen Verweis auf die Instanz zurück. (Das Problem tritt auf. Zu diesem Zeitpunkt ist der Hinweis auf die Instanz keine Singleton -Instanz, da der Konstruktor nicht aufgerufen wird.)
6. Thread B verlässt und fädelt sich ein.
7. Thread A ruft weiterhin die Konstruktormethode auf, vervollständigt die Initialisierung der Instanz und kehrt zurück.
Gibt es keinen guten Weg? Es muss einen guten Weg geben, lasst uns weiter erkunden!
// das Problem des ungeordneten Schreibens fauler öffentlicher Klasse Singleton {// Singleton Instance Variable Private static Singleton Instance = null; // private Konstruktionsmethode, um sicherzustellen, dass externe Klassen nicht über Constructor private Singleton () {} // Singleton Instance Public static Singleton getInstance () {if (instance == null) {synchronized (Singleton.class) {// 1 Singleton temp = instance; // 2 if (temp == null) {synchronized (Singleton.class) {// 3 temp = new Singleton (); // 4} Instance = temp; // 5}}} System.out.println ("Ich löste das ungeordnete Schreiben fauler Singletons!"); Rückkehrinstanz; }} 1. Faden A GEBEN DIE GETINSTANCE () -Methode.
2. Da die Instanz leer ist, tritt ein Faden in den ersten synchronisierten Block in Position // 1 ein.
3. Thread A führt den Code an der Position // 2 aus und weist der lokalen variablen Temperatur eine Instanz zu. Die Instanz ist leer, also ist die Temperatur ebenfalls leer.
V. (Ich dachte, dass dieses Schloss etwas überflüssig war)
5. Thread A führt den Code an der Position // 4 aus und setzt Tempor in nicht leeres, aber der Konstruktor wurde noch nicht aufgerufen! ("Unbestrehstes Schreiben" -Problem))
6. Wenn der Faden A Blöcke blockiert, tritt Thread B die Methode getInstance () ein.
7. Da die Instanz leer ist, versucht Thread B, den ersten synchronisierten Block einzugeben. Aber weil Thread A bereits drinnen ist. Es ist also unmöglich einzugeben. Faden -B -Blöcke.
8. Thread A wird aktiviert und führt den Code weiter in Position // 4 aus. Rufen Sie den Konstruktor an. Eine Instanz generieren.
9. Weisen Sie die Instanzreferenz der TEMP auf die Instanz zu. Beenden Sie zwei synchronisierte Blöcke. Gibt die Instanz zurück.
10. Thread B wird aktiviert und tritt in den ersten synchronisierten Block ein.
11. Thread B führt den Code an der Position // 2 aus und weist die Instanzinstanz der lokalen Temp -Variablen zu.
12. Thread B bestimmt, dass die lokale variable Temperatur nicht leer ist, also überspringt der IF -Block. Gibt die Instanzinstanz zurück.
Bisher haben wir das oben genannte Problem gelöst, aber wir haben plötzlich festgestellt, dass es so anfühlt, als ob viel Garn am Körper umwickelt ist ... es ist chaotisch, also müssen wir es rationalisieren:
// Hungry Public Class Singleton {// Singleton Variable, statisch, wird einmal initialisiert, wenn die Klasse geladen wird, um die Sicherheit von Thread Safety Private statische Singleton Instance = new Singleton () zu gewährleisten; // private Baumethode, um sicherzustellen, dass externe Klassen nicht über den Konstruktor instanziiert werden können. private Singleton () {} // Holen Sie sich die Singleton -Objektinstanz Public static Singleton getInstance () {System.out.println ("Ich bin ein hungriger Singleton!"); Rückkehrinstanz; }}Als ich den Code oben sah, hatte ich sofort das Gefühl, dass die Welt ruhig war. Diese Methode übernimmt jedoch die hungrige Methode im Man-Stil, bei der Singleton-Objekte vor der Deklare vorhanden sind. Ein Nachteil davon ist: Wenn der Singleton des Bauwerks groß ist und nach Abschluss des Baues nicht verwendet wird, wird er zu Ressourcenverschwendung führen.
Gibt es einen perfekten Weg? Weiter zuschauen:
// Die innere Klasse implementiert Lazy Public Class Singleton {private statische Klasse Singletonholder {// Singleton Variable Private statische Singleton Instance = new Singleton (); } // Private Konstruktionsmethode, um sicherzustellen, dass externe Klassen nicht über den Konstruktor instanziiert werden können. private Singleton () {} // Singleton -Objektinstanz öffentliche statische Singleton getInstance () {System.out.println ("Ich bin ein interner Klassen -Singleton!"); return Singletonholder.instance; }}Faul (oben vermeiden Ressourcenabfälle), Thread-Safe und einfacher Code. Da der Java-Mechanismus feststellt, dass der interne Klassen-Singletonholder nur dann geladen wird, wenn die Methode getInstance () zum ersten Mal aufgerufen wird (implementieren faul), und der Ladevorgang ist Thread-safe (implementierende Thread-Safe). Die Instanz wird instanziiert, wenn die interne Klasse geladen wird.
Lassen Sie uns kurz über das nicht ordnungsgemäße Schreiben sprechen, das oben erwähnt wird. Dies ist das Merkmal von JVM. Zum Beispiel zwei Variablen deklarieren, Zeichenfolge A; String B; JVM kann eine erste oder b laden. In ähnlicher Weise Instanz = new Singleton (); kann eine Instanz auf Nicht-Leerzeichen setzen, bevor er den Konstruktor von Singleton bezeichnet. Dies ist eine Frage für viele Menschen, in denen ein Objekt von Singleton nicht instanziiert wurde. Wie wurde die Instanz nicht leer? Was ist ihr Wert jetzt? Wenn Sie dieses Problem verstehen möchten, müssen Sie verstehen, wie die Satzinstanz = new Singleton (); wird ausgeführt. Hier ist ein Pseudo-Code, um es Ihnen zu erklären:
mem = allocate (); // Speicher für Singleton -Objekte Gedächtnis zuweisen. Instance = mem; // Beachten Sie, dass die Instanz jetzt nicht leer ist, aber noch nicht initialisiert wurde. ctorsingleton (Instanz); // Rufen Sie den Konstruktor von Singleton und Passinstanz an.
Es ist ersichtlich, dass, wenn ein Thread Instance = mem; Instanz ausführt, nicht leer ist. Wenn ein anderer Thread in das Programm und die Richterinstanz als nicht leer eingeht, wird er zur Rückkehrinstanz springen. Und zu diesem Zeitpunkt hat Singletons Konstruktor nicht als Instanz bezeichnet, und der aktuelle Wert ist das von Allocate () zurückgegebene Speicherobjekt. Der zweite Thread erhält also nicht ein Singleton -Objekt, sondern ein Speicherobjekt.
Das obige ist meine kleinen Gedanken und mein Verständnis des Singleton -Modells. Ich begrüße sehr alle großen Götter, um zu kommen und zu führen und zu kritisieren.