1. Singleton-Klasse im Hungry-Stil
}
private static Singleton-Instanz = new Singleton();
privates statisches Singleton getInstance(){
Rückgabeinstanz;
}
}
Features: Instanziierung im hungrigen Stil im Voraus, im Lazy-Stil gibt es kein Multithreading-Problem, aber egal, ob wir getInstance() aufrufen oder nicht, es wird eine Instanz im Speicher geben
2. Interne Klassen-Singleton-Klasse
}
private Klasse SingletonHoledr(){
private static Singleton-Instanz = new Singleton();
}
privates statisches Singleton getInstance(){
return SingletonHoledr.instance;
}
}
Funktionen: In der internen Klasse wird Lazy Loading implementiert. Nur wenn wir getInstance() aufrufen, wird auch das Problem des Multithreadings im Lazy-Stil gelöst .
3. Lazy Singleton-Klasse
}
private statische Singleton-Instanz;
öffentliches statisches Singleton getInstance(){
if(instance == null){
return-Instanz = new Singleton();
}anders{
Rückgabeinstanz;
}
}
}
Features: Im Lazy-Stil gibt es Threads A und B. Wenn Thread A zu Zeile 8 läuft, springt er zu Thread B. Wenn B auch zu Zeile 8 läuft, sind die Instanzen beider Threads leer, sodass zwei Beispiele generiert werden . Die Lösung besteht darin, Folgendes zu synchronisieren:
Synchronisierung ist möglich, aber nicht effizient:
}
private statische Singleton-Instanz;
öffentlicher statischer synchronisierter Singleton getInstance(){
if(instance == null){
return-Instanz = new Singleton();
}anders{
Rückgabeinstanz;
}
}
}
Beim Schreiben eines solchen Programms treten keine Fehler auf, da die gesamte getInstance ein ganzer „kritischer Abschnitt“ ist. Die Effizienz ist jedoch sehr gering, da unser Zweck eigentlich nur darin besteht, die Instanz beim ersten Initialisieren und dann zu sperren Wenn Sie eine Instanz verwenden, ist überhaupt keine Thread-Synchronisierung erforderlich.
Also haben sich kluge Köpfe den folgenden Ansatz ausgedacht:
Überprüfen Sie die Schreibmethode für das Schloss noch einmal:
public static Singleton getSingle(){ //Externe Objekte können über diese Methode abgerufen werden
if(single == null){
synchronisiert (Singleton.class) { // Es stellt sicher, dass nur ein Objekt gleichzeitig auf diesen synchronisierten Block zugreifen kann
if(single == null){
single = new Singleton();
}
}
}
return single; //Das erstellte Objekt zurückgeben
}
}
Die Idee ist sehr einfach, das heißt, wir müssen nur den Teil des Codes synchronisieren (synchronisieren), der die Instanz initialisiert, damit der Code sowohl korrekt als auch effizient ist.
Hierbei handelt es sich um den sogenannten „Double-Check-Lock“-Mechanismus (wie der Name schon sagt).
Leider ist diese Schreibweise auf vielen Plattformen und optimierenden Compilern falsch.
Der Grund dafür ist, dass das Verhalten der Codezeile „instance = new Singleton()“ auf verschiedenen Compilern unvorhersehbar ist. Ein optimierender Compiler kann Instanz = new Singleton() wie folgt legal implementieren:
1. Instanz = Speicher für die neue Entität zuweisen
2. Rufen Sie den Singleton-Konstruktor auf, um die Instanzmitgliedsvariablen zu initialisieren
Stellen Sie sich nun vor, dass Thread A und B getInstance aufrufen. Thread A tritt zuerst ein und wird aus der CPU geworfen, wenn Schritt 1 ausgeführt wird. Dann tritt Thread B ein und was B sieht, ist, dass die Instanz nicht mehr null ist (Speicher wurde zugewiesen), sodass er beginnt, die Instanz mit Zuversicht zu verwenden. Dies ist jedoch falsch, da die Mitgliedsvariablen der Instanz in diesem Moment immer noch Standard sind Wert, hatte A noch keine Zeit, Schritt 2 auszuführen, um die Initialisierung der Instanz abzuschließen.
Natürlich kann der Compiler es auch so implementieren:
1. temp = Speicher zuweisen
2. Rufen Sie den Konstruktor von temp auf
3. Instanz = temp
Wenn sich der Compiler so verhält, scheinen wir kein Problem zu haben, aber die Tatsache ist nicht so einfach, weil wir nicht wissen können, wie ein bestimmter Compiler das macht, weil dieses Problem nicht im Speichermodell von Java definiert ist.
Die doppelte Prüfsperre gilt für Basistypen (z. B. int). Offensichtlich, weil der Basistyp den Konstruktor nicht aufruft.