1. Was ist der Singleton -Modus
Das Singleton -Muster bezieht sich auf die Existenz von nur einer Instanz während des gesamten Lebens der Anwendung. Singleton -Muster ist ein weit verbreitetes Designmuster. Es hat viele Vorteile, die die doppelte Erstellung von Instanzobjekten vermeiden, den Systemaufwand des Erstellens von Instanzen verringern und Speicher speichern.
Es gibt drei Anforderungen für den Singleton -Modus:
2. Der Unterschied zwischen Singleton -Muster und statischer Klasse
Lassen Sie uns zunächst verstehen, was eine statische Klasse ist. Eine statische Klasse bedeutet, dass eine Klasse statische Methoden und statische Felder hat. Der Konstruktor wird durch privat modifiziert und kann daher nicht instanziiert werden. Die Matheklasse ist eine statische Klasse.
Nachdem wir wissen, was eine statische Klasse ist, sprechen wir über den Unterschied zwischen ihnen:
1) Zunächst einmal bietet das Singleton -Muster Ihnen ein weltweit einzigartiges Objekt. Die statische Klasse bietet Ihnen nur viele statische Methoden. Diese Methoden müssen nicht erstellt werden und können direkt durch die Klasse aufgerufen werden.
2) Das Singleton -Muster hat eine höhere Flexibilität, und Methoden können überschrieben werden, da statische Klassen alle statischen Methoden sind, sodass sie nicht außer Kraft gesetzt werden können.
3) Wenn es sich um ein sehr schweres Objekt handelt, kann das Singleton -Muster faul zum Laden sein, aber statische Klassen können es nicht tun.
Dann sollten statische Klassen verwendet werden, und wann sollten wir den Singleton -Modus verwenden? Wenn Sie nur einige Werkzeugmethoden verwenden möchten, verwenden Sie zunächst am besten statische Klassen. Statische Analogien sind schneller als Singleton -Klassen, da während der Zusammenstellungsperiode statische Bindung durchgeführt wird. Wenn Sie Statusinformationen oder Zugriffsressourcen beibehalten möchten, sollten Sie den Singleton -Modus verwenden. Es kann auch gesagt werden, dass wenn Sie objektorientierte Fähigkeiten (wie Vererbung, Polymorphismus) benötigen, Singleton-Klassen wählen und wenn Sie nur einige Methoden angeben, wählen Sie statische Klassen.
3. So implementieren Sie den Singleton -Modus
1. Hungry Man -Modus
Der sogenannte hungrige Modus soll sofort geladen werden. Im Allgemeinen wurden Fälle generiert, bevor die GetInstancef -Methode aufgerufen wurde, was bedeutet, dass sie beim Laden der Klasse generiert wurde. Der Nachteil dieses Modells ist sehr offensichtlich, dass es Ressourcen besetzt. Wenn die Singleton -Klasse groß ist, möchten wir sie tatsächlich verwenden und dann Instanzen generieren. Daher ist diese Methode für Klassen geeignet, die weniger Ressourcen belegen und während der Initialisierung verwendet werden.
Klasse Singletonhungary {private statische Singletonhungary Singletonhungary = New Singletonhungary (); // Setzen Sie den Konstruktor auf privat, um die Instanziierung durch neue private Singletonhungary () {} public static Singletonhungary GetInstance () {return Singletonhungary; }}2. Lazy Modus
Der faulen Modus ist faul Laden, auch als fauler Laden bezeichnet. Erstellen Sie eine Instanz, wenn das Programm verwendet werden muss, damit der Speicher nicht verschwendet wird. Für den faulen Modus finden Sie hier 5 Implementierungsmethoden. Einige Implementierungsmethoden sind Thread-Iscure, was bedeutet, dass in einer Umgebung mit Multi-Threaded-Parallelität Ressourcensynchronisationsprobleme auftreten können.
Erstens ist die erste Methode, dass es kein Problem im einzelnen Thread gibt, aber es wird Probleme beim Multi-Threading geben.
// Lazy Implementierung des Singleton-Modus 1-Thread Unafe Class SingletonLazy1 {private statische SingletonLazy1 SingletonLazy; private SingletonLazy1 () {} public static SingletOnLazy1 getInstance () {if (null == SingletonLazy) {try {// Simulation einer Vorbereitung, bevor er den Objekt -Thread erstellt.sleep (1000); } catch (interruptedException e) {e.printstacktrace (); } SingletonLazy = new SingletonLazy1 (); } return SingletonLazy; }}Lassen Sie uns 10 asynchrone Threads simulieren, um zu testen:
öffentliche Klasse SingletonLazyTest {public static void main (String [] args) {Thread2 [] threadarr = new Thread2 [10]; für (int i = 0; i <threadarr.length; i ++) {threadarr [i] = new Thread2 (); Threadarr [i] .Start (); }}} // Thread Class Thread2 erweitert Thread {@Override public void run () {System.out.println (SingletonLazy1.getInstance (). HashCode ()); }}Auslaufergebnisse:
124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303
Sie können sehen, dass ihre Hashcodes nicht alle gleich sind, was bedeutet, dass mehrere Objekte in einer Umgebung mit mehreren Threaden erzeugt werden, was nicht den Anforderungen des Singleton-Musters entspricht.
Wie kann man Faden sicher machen? In der zweiten Methode verwenden wir das synchronisierte Schlüsselwort, um die GetInstance -Methode zu synchronisieren.
// Singleton-Modus Lazy Implementierung 2-Sicherheit von Thread // Durch Einstellen der Synchronisierungsmethode ist die Effizienz zu niedrig. Die gesamte Methode ist gesperrt klassen SingletOnlazy2 {private statische SingletonLazy2 SingletonLazy; private SingletOnLazy2 () {} public static synchronisierte SingletOnLazy2 getInstance () {try {if (null == SingletonLazy) {// simulieren, um vor dem Erstellen des Objekt -Threads (1000) eine Vorbereitung durchzuführen. SingletonLazy = new SingletonLazy2 (); }} catch (interruptedException e) {e.printstacktrace (); } return SingletonLazy; }}Unter Verwendung der obigen Testklasse die Testergebnisse:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
Wie zu sehen ist, erreicht diese Methode die Gewindesicherheit. Der Nachteil ist jedoch, dass die Effizienz zu niedrig ist und synchron läuft. Wenn der nächste Thread das Objekt erhalten möchte, muss es warten, bis der vorherige Thread veröffentlicht wird, bevor es weiter ausgeführt werden kann.
Dann können wir die Methode nicht sperren, sondern den Code im Inneren sperren, was auch die Gewindesicherheit erreichen kann. Diese Methode entspricht jedoch der Synchronisationsmethode und wird auch synchron ausgeführt und hat eine sehr geringe Effizienz.
// SingletOnLazy Implementierung 3-Thread Safety // Durch Einstellen von synchronen Codeblöcken ist die Effizienz zu niedrig und der gesamte Codeblock ist gesperrt. private SingletonLazy3 () {} public static SingletOnLazy3 getInstance () {try {synchronized (SingletonLazy3.class) {if (null == SingletOnLazy) {// simuliert, um ein Objektthread vorzubereiten. SingletonLazy = new SingletonLazy3 (); }}} catch (InterruptedException e) {// todo: Handle Exception} return SingletonLazy; }}Lassen Sie uns den Code weiter optimieren. Wir sperren nur den Code, der das Objekt erstellt, aber kann dies jedoch sicherstellen, dass die Thread -Sicherheit sichergestellt wird?
// Lazy Implementierung des Singleton-Modus 4-Thread unsicher // Nur der Code, der Instanzen erstellt, wird synchronisiert, indem Synchronisierungscode-Blöcke eingestellt werden //, aber es gibt immer noch Thread-Sicherheitsprobleme Klassen SingletonLazy4 {private statische SingletonLazy4 SingletonLazy; private SingletOnLazy4 () {} public static SingletOnLazy4 getInstance () {try {if (null == SingletonLazy) {// Code 1 // simulieren, um vor dem Erstellen eines Objekt -Threads (1000) eine Vorbereitung vorzubereiten. synchronisiert (SingletonLazy4.Class) {SingletonLazy = new SingletonLazy4 (); // Code 2}}} catch (InterruptedException e) {// todo: Behandle -Ausnahme} return SingletonLazy; }}Schauen wir uns die laufenden Ergebnisse an:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
Nach den Ergebnissen kann diese Methode die Sicherheit der Threads nicht garantieren. Warum? Nehmen wir an, dass zwei Threads A und B gleichzeitig zu 'Code 1' gehen, da das Objekt zu diesem Zeitpunkt noch leer ist, damit beide die Methode eingeben können. Fädeln Sie sich zuerst das Schloss und erstellen Sie das Objekt. Nachdem Thread B das Schloss erhalten hat, wird es auch zu 'Code 2' gilt, wenn es veröffentlicht wird, und ein Objekt wird erstellt. Daher kann ein Singleton in einer Umgebung mit mehreren Threaden nicht garantiert werden.
Lassen Sie uns weiter optimieren. Da es ein Problem mit der obigen Methode gibt, können wir im Synchronisationscode -Block nur ein Nullurteil fällen. Diese Methode ist unser DCL -Double Check -Sperrmechanismus.
// Slazy Man im Singleton-Modus implementiert 5-Thread-Sicherheit // Durch Einstellen des Synchronisierungscodeblocks den DCL-Doppel-Überprüfungs-Sperrmechanismus // Verwenden des Double Check-Sperrmechanismus löst erfolgreich die Thread-Unsicherheit und die Effizienzprobleme, die durch den faulen Mann im Singleton-Modus im Einsatz des Lazy Man im Singleton-Modus verwendet werden. Wenn das SingletonLazy5 -Objekt erstellt wird und das SingletonLazy5 -Objekt erhalten wird, müssen die Sperre des Synchronisierungscodeblocks und den nachfolgenden Code nicht verifiziert und das SingletonLazy5 -Objekt direkt zurückgegeben. Klasse SingletonLazy5 {private statische volatile SingletonLazy5 SingletonLazy; private SingletOnLazy5 () {} public static SingletOnLazy5 getInstance () {try {if (null == SingletonLazy) {// Simulieren Sie eine Vorbereitung, bevor Sie den Objekt -Thread erstellen.sleep (1000); synchronized (SingletonLazy5.Class) {if (null == SingletonLazy) {SingletonLazy = new SingletonLazy5 (); }}}} catch (InterruptedException e) {} return SingletonLazy; }}Auslaufergebnisse:
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
Wir können sehen, dass der DCL -Double -Check -Sperrmechanismus die Effizienz- und Gewindesicherheitsprobleme des faulen Ladens Singleton -Modus löst. Dies ist auch die Methode, die wir am häufigsten anwenden.
Volatile Keyword
Hier bemerkte ich, dass beim Definieren von SingletonLazy das volatile Schlüsselwort verwendet wird. Dies soll verhindern, dass Anweisungen neu ordnen. Warum müssen wir das tun? Schauen wir uns ein Szenario an:
Der Code geht an SingletonLazy = new SingletonLazy5 (); Es scheint ein Satz zu sein, aber dies ist keine atomare Operation (entweder werden alle ausgeführt oder alle nicht ausgeführt und die Hälfte kann nicht ausgeführt werden). Dieser Satz wird in 8 Anweisungen der Baugruppe zusammengestellt, und ungefähr 3 Dinge werden durchgeführt:
1. Zulassen Sie dem SingletonLazy5 -Instanz Erinnerung.
2. Initialisieren Sie den SingletonLazy5 -Konstruktor
3. Zeigen Sie das SingletonLazy -Objekt auf den zugewiesenen Speicherraum (beachten Sie, dass diese Instanz nicht null ist).
Da der Java-Compiler dem Prozessor ermöglicht, außerordentlich (außerordentlich) und die Reihenfolge des Cache auszuführen, registriert die Hauptspeicher-Schreibback in JMM (Java Memory Medel) vor JDK1.5, die Reihenfolge der zweiten und dritten Punkte oben nicht garantiert werden. Das heißt, die Ausführungsreihenfolge kann 1-2-3 oder 1-3-2 betragen. Wenn es sich um das letztere handelt und bevor 3 ausgeführt und 2 nicht ausgeführt wird, wird es auf Thread 2 umgestellt. Zu diesem Zeitpunkt hat SingletonLazy bereits den dritten Punkt in Thread 1 ausgeführt, SingletonLazy ist bereits nicht leer, sodass zwei direkt SingletonLazy nimmt, dann verwendet und dann ein Fehler auf natürliche Weise berichtet. Darüber hinaus ist diese Art von Fehler, die schwer zu verfolgen und schwer zu reproduzieren ist, in der letzten Woche des Debuggens möglicherweise nicht zu finden.
Die Schreibmethode von DCL zur Implementierung von Singletons wird in vielen technischen Büchern und Lehrbüchern (einschließlich Bücher basierend auf früheren Versionen von JDK1.4) empfohlen, ist jedoch tatsächlich nicht vollständig korrekt. In der Tat ist DCL in einigen Sprachen (z. B. c) machbar, je nachdem, ob die Reihenfolge von 2 und 3 Schritten garantiert werden kann. Nach JDK1.5 hat der Beamte dieses Problem bemerkt, so dass das JMM angepasst wurde und das volatile Schlüsselwort konkret ist. Wenn JDK eine Version von 1.5 oder später ist, müssen Sie daher nur das volatile Schlüsselwort zur Definition von SingletonLazy hinzufügen, die sicherstellen kann, dass SingletonLazy jedes Mal aus der Hauptspeicher gelesen wird, und das Nachbestellen kann verboten werden, und Sie können die DCL -Schreibmethode verwenden, um den Singleton -Modus zu vervollständigen. Natürlich wirkt sich volatil mehr oder weniger die Leistung aus. Das Wichtigste ist, dass wir auch JDK1.42 und frühere Versionen berücksichtigen müssen, sodass die Verbesserung des Singleton -Muster -Schreibens immer noch fortgesetzt wird.
3. statische innere Klasse
Basierend auf den obigen Überlegungen können wir statische innere Klassen verwenden, um das Singleton -Muster zu implementieren. Der Code ist wie folgt:
// Singleton-Modus mit statischer interner Klassen-Thread-Sicherheitsklasse SingletonstaticInner {private SingletonstaticInner () {} private statische Klasse SingletonInner {privat statischer SingletonstaticInner SingletonstaticInner = New SingletonstaticInner (); } public static SingletonStaticInner getInstance () {try {thread.sleep (1000); } catch (InterruptedException e) {// Todo automatisch generierter Catch-Block e.printstacktrace (); } return SingletonInner.singletonstaticInner; }}Es ist zu sehen, dass wir auf diese Weise keine Synchronisationsvorgänge ausdrücklich ausführen. Wie gewährleistet dies also die Sicherheit von Threads? Wie im Hungry Man -Modus ist es eine Funktion, dass JVM sicherstellt, dass die statischen Mitglieder der Klasse nur einmal geladen werden können, so dass nur ein Instanzobjekt von der JVM -Ebene vorhanden ist. Die Frage ist also, was ist der Unterschied zwischen dieser Methode und dem Hungry Man -Modell? Ist es nicht sofort geladen? Wenn eine Klasse geladen ist, wird die innere Klasse nicht gleichzeitig geladen. Eine Klasse wird geladen, die nur dann auftritt, wenn eines seiner statischen Mitglieder (statische Domänen, Konstruktoren, statische Methoden usw.) aufgerufen wird.
Es kann gesagt werden, dass diese Methode die optimale Lösung ist, um das Singleton -Muster zu implementieren.
4. Statische Codeblöcke
Hier ist ein statisches Code -Block -Implementierungs -Singleton -Muster. Diese Methode ähnelt dem ersten und es ist auch ein hungriges Mannmodell.
// Verwenden Sie statische Codeblöcke, um die SingletonstaticBlock der Singleton -Modus zu implementieren. static {SingletonstaticBlock = new SingletonstaticBlock (); } public static SingletonstaticBlock getInstance () {return SingletonstaticBlock; }}5. Serialisierung und Deserialisierung
Warum empfiehlt LZ Serialisierung und Deserialisierung? Denn obwohl der Singleton -Modus die Sicherheit der Fäden sicherstellen kann, werden im Fall von Serialisierung und Deserialisierung mehrere Objekte erzeugt. Führen Sie die folgende Testklasse aus,
public class SingletonstaticInnerializeTest {public static void main (String [] args) {try {SingletonstaticInnernerialize serialize = SingletonstaticInnerialize.getInstance (); System.out.println (Serialize.hashCode ()); // serialize FileOutputStream fo = new FileOutputStream ("TEM"); ObjectOutputStream oo = new ObjectOutputStream (fo); oo.writeObject (serialize); oo.close (); fo.close (); // Deserialize FileInputStream fi = new FileInputStream ("TEM"); ObjectInputStream oi = new ObjectInputStream (FI); SingletonStaticInnerSerialize serialize2 = (SingletonstaticInnerSerialize) oi.ReadObject (); oi.close (); fi.close (); System.out.println (Serialize2.HashCode ()); } catch (Ausnahme e) {e.printstacktrace (); }}} // Verwenden Sie anonyme interne Klassen, um das Singleton -Muster zu implementieren. Bei der Begegnung von Serialisierung und Deserialisierung wird dieselbe Instanz nicht erhalten. Klasse SingletonStaticInnernerialize implementiert serialisierbar { / *** 28. März 2018* / Private statische endgültige lange Serialversionuid = 1L; private statische Klasse Innerclass {private statische SingletonstaticInnernerialize SingletonstaticInnerialize = New SingletonstaticInnerSerialize (); } public static sinlletonstaticinnerialize getInstance () {return Innerclass.singletonStaticInnerSerialize; } // Protected Object ReadResolve () {// system.out.println ("Die ReadResolve -Methode wurde genannt"); // Innerclass.singletonStaticInnerialize; //}}Sie können sehen:
865113938
1078694789
Die Ergebnisse zeigen, dass es sich tatsächlich um zwei verschiedene Objektinstanzen handelt, die gegen das Singleton -Muster verstoßen. Wie kann man dieses Problem lösen? Die Lösung besteht darin, die ReadResolve () -Methode in der Deserialisierung zu verwenden, den obigen Kommentarcode zu entfernen und erneut auszuführen:
865113938
Die ReadResolve -Methode wurde genannt
865113938
Die Frage ist, wer ist der heilige Weg der ReadResolve () -Methode? Wenn das JVM ein neues Objekt aus dem Speicher deserialisiert und "zusammenstellt", wird diese ReadResolve -Methode automatisch aufgerufen, um das von uns angegebene Objekt zurückzugeben, und die Singleton -Regeln sind garantiert. Die Entstehung von ReadResolve () ermöglicht es den Programmierern, die durch Deserialisierung erhaltenen Objekte selbst zu steuern.
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, es wird für das Lernen aller hilfreich sein und ich hoffe, jeder wird Wulin.com mehr unterstützen.