Heim >Java >javaLernprogramm >Doppelt überprüftes Sperren und verzögerte Initialisierung
Der Ursprung der Doppelprüfungssperre
In Java-Programmen ist es manchmal erforderlich, die Initialisierung einiger teurer Objekte zu verschieben und sie erst dann zu initialisieren, wenn das Objekt tatsächlich verwendet wird. Zu diesem Zeitpunkt ist die verzögerte Initialisierung erforderlich Technologie ist erforderlich.
Die korrekte Implementierung der verzögerten Initialisierung erfordert einige Fähigkeiten, andernfalls kann es zu Problemen kommen, die im Folgenden einzeln vorgestellt werden.
Option 1
public class UnsafeLazyInit{ private static Instance instance; public static Instance getInstance(){ if (instance == null){ instance = new Instance(); } return instance; } }
Der Fehler dieses Ansatzes ist offensichtlich. Wenn zwei Threads getInstance separat aufrufen, ist dies in den folgenden beiden Situationen einfach, da der Zugriff auf gemeinsam genutzte Variablen nicht synchronisiert ist auftreten: Situation:
1. Threads A und B haben beide festgestellt, dass die Instanz nicht initialisiert wurde, also haben sie sie entsprechend initialisiert.
2. Instanz=neue Instanzoperation wird neu angeordnet. Der eigentliche Ausführungsprozess kann sein: Zuerst Speicher zuweisen, dann der Instanz einen Wert zuweisen und schließlich die Initialisierung durchführen. Wenn dies der Fall ist, lesen andere Threads möglicherweise das Instanzobjekt, das noch nicht initialisiert wurde.
Option 2
public class UnsafeLazyInit{ private static Instance instance; public static synchronized Instance getInstance(){ if (instance == null){ instance = new Instance(); } return instance; } }
Das Problem bei diesem Ansatz ist offensichtlich. Jedes Mal, wenn die Instanz gelesen wird, muss sie synchronisiert werden, was einen größeren Einfluss auf die Leistung haben kann.
Option 3
Option 3 ist eine falsche Implementierung der Doppelerkennungssperre. Schauen Sie sich den Code an:
public class UnsafeLazyInit{ private static Instance instance; public static Instance getInstance(){ if (instance == null){ synchronized(UnsafeLazyInit.classs){ if (instance == null){ instance = new Instance(); } } } return instance; } }
Diese Lösung scheint die beiden oben genannten Lösungen zu lösen Probleme, aber es gibt auch Probleme.
Quelle des Problems
instance = new Instance();
In der tatsächlichen Ausführung kann diese Anweisung wie folgt in drei Anweisungen aufgeteilt werden:
memory = allocate(); ctorInstance(memory); //2 instance = memory; //3
Gemäß den Neuordnungsregeln haben die letzten beiden Anweisungen keine Datenabhängigkeit, sodass sie neu angeordnet werden können.
Nach der Neuordnung bedeutet dies, dass nach der Zuweisung des Instanzfelds das Objekt, auf das verwiesen wird, möglicherweise noch nicht initialisiert ist und das Instanzfeld ein statisches Feld ist, das von anderen Threads und dann von anderen Threads gelesen werden kann Die Instanz Feld, das noch nicht initialisiert wurde, kann gelesen werden.
Volatile-basierte Lösung
Um dieses Problem zu lösen, müssen Sie nur die Neuordnung von Anweisung 2 und Anweisung 3 verbieten, sodass Sie flüchtig zum Ändern der Instanz verwenden können.
private volatile static Instance-Instanz;
Weil die volatile-Semantik es dem Compiler verbietet, Vorgänge neu anzuordnen, bevor volatile nach volatile schreibt.
Lösung basierend auf der Klasseninitialisierung
Die Java-Sprachspezifikation legt fest, dass für jede Klasse oder Schnittstelle C eine eindeutige entsprechende Initialisierungssperre LC vorhanden ist, die von C auf LC abgebildet wird. Implementiert von JVM. Wenn jeder Thread Informationen über eine Klasse liest und die Klasse nicht initialisiert wurde, versucht er, den LC abzurufen, um ihn zu initialisieren. Wenn die Erfassung fehlschlägt, wartet er darauf, dass andere Threads den LC freigeben. Wenn LC erhalten werden kann, muss der Initialisierungsstatus der Klasse ermittelt werden. Wenn es sich um eine Bit-Initialisierung handelt, muss eine Initialisierung durchgeführt werden. Wenn es initialisiert wird, warten Sie, bis andere Threads die Initialisierung abgeschlossen haben. Wenn es bereits initialisiert wurde, verwenden Sie diesen Objekttyp direkt.
public class InstanceFactory{ private static class InstanceHolder{ public static Instance = new Instance(); } public static Instance getInstance(){ return InstanceHolder.instance; //这里将导致instance类被初始化 } }
Fazit
Die verzögerte Initialisierung von Feldern verringert den Aufwand für die Initialisierung von Klassen oder die Erstellung von Instanzen, erhöht jedoch den Aufwand für den Nullzugriff auf verzögert initialisierte Felder. In den meisten Fällen ist die normale Initialisierung besser als die verzögerte Initialisierung. Wenn Sie wirklich eine Thread-sichere Lazy-Initialisierung für Instanzfelder benötigen, verwenden Sie bitte das oben beschriebene flüchtige Lazy-Initialisierungsschema. Wenn Sie wirklich eine Thread-sichere Lazy-Initialisierung für statische Felder verwenden müssen, verwenden Sie bitte die Lazy-Initialisierung basierend auf Klasseninitialisierungsschema oben.