Aus dem Head First- Entwurfsmusterbuch wurde das Singleton-Muster mit doppelt überprüfter Verriegelung wie folgt implementiert:
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Ich verstehe nicht, warum volatile
verwendet wird. Besiegt die volatile
Verwendung nicht den Zweck der Verwendung von doppelt überprüftem Sperren, dh Leistung?
volatile
(wie in "wird dein Programm nicht vermasseln"), aber dann gewinnst du nicht wirklich viel.Antworten:
Eine gute Quelle, um zu verstehen, warum dies
volatile
erforderlich ist, ist das JCIP- Buch. Wikipedia hat auch eine anständige Erklärung für dieses Material.Das eigentliche Problem besteht darin, dass
Thread A
möglicherweise ein Speicherplatz zugewiesen wird,instance
bevor die Erstellung abgeschlossen istinstance
.Thread B
wird diese Zuordnung sehen und versuchen, sie zu verwenden. Dies führt zu einemThread B
Fehler, da eine teilweise erstellte Version von verwendet wirdinstance
.quelle
volatile
ist langsamer als ein normales Feld . Wenn Sie nach Leistung suchen, entscheiden Sie sich für ein Muster der Halterklasse.volatile
ist nur hier, um zu zeigen, dass es einen Weg gibt , das gebrochene Muster zum Laufen zu bringen. Es ist eher eine Codierungsherausforderung als ein echtes Problem.Foo.getInstance()
ist nur ein Ausdruck, um irgendwie ein Foo zu bekommen, es unterscheidet sich nicht von@Inject Foo foo
; In beiden Fällen ist die Site, die ein Foo anfordert, unabhängig davon, welches Foo zurückgegeben wird und wie, in beiden Fällen sind statische und Laufzeitabhängigkeiten gleich.static
factory
ist die Versuchung, es tief im Code zu nennen, der diegetInstance()
Methode nicht kennen und stattdessen die Bereitstellung einer Instanz verlangen sollte.The real problem is that Thread A may assign a memory space for instance before it is finished constructing instance.
Wie kann dasvolatile
gelöst werden?Wie von @irreputable angegeben, ist flüchtig nicht teuer. Auch wenn es teuer ist, sollte der Konsistenz Vorrang vor der Leistung eingeräumt werden.
Es gibt noch einen sauberen, eleganten Weg für Lazy Singletons.
public final class Singleton { private Singleton() {} public static Singleton getInstance() { return LazyHolder.INSTANCE; } private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } }
Quellartikel : Initialization-on-Demand_holder_idiom aus Wikipedia
Da die Klasse keine
static
zu initialisierenden Variablen hat, wird die Initialisierung trivial abgeschlossen.Die darin enthaltene statische Klassendefinition
LazyHolder
wird erst initialisiert, wenn die JVM feststellt, dass LazyHolder ausgeführt werden muss.Die statische Klasse
LazyHolder
wird nur ausgeführt, wenn die statische MethodegetInstance
für die Klasse Singleton aufgerufen wird. Wenn dies zum ersten Mal geschieht, lädt und initialisiert die JVM dieLazyHolder
Klasse.Diese Lösung ist threadsicher, ohne dass spezielle Sprachkonstrukte (dh
volatile
odersynchronized
) erforderlich sind .quelle
Nun, es gibt keine doppelt überprüfte Sperre für die Leistung. Es ist ein gebrochenes Muster.
Emotionen beiseite zu lassen,
volatile
ist hier, weil ohne sie zum Zeitpunkt des Ablaufs des zweiten Threads derinstance == null
erste Thread möglicherweise noch nicht konstruiertnew Singleton()
ist: Niemand verspricht, dass die Erstellung des Objekts erfolgt - vor der Zuweisunginstance
für einen Thread, außer demjenigen, der das Objekt tatsächlich erstellt.volatile
wiederum gründet geschieht zuvor Beziehung zwischen Lese- und Schreibvorgänge, und fixiert das gebrochene Muster.Wenn Sie nach Leistung suchen, verwenden Sie stattdessen die innere statische Klasse des Halters.
quelle
instance
Variablen und die tatsächliche Erstellung des Objekts an dieser Adresse. Sofern die Synchronisierung nicht erzwungen wird, z. B. einvolatile
Zugriff oder eine explizite Synchronisierung, kann der zweite Thread diese beiden Ereignisse außer Betrieb setzen.Wenn Sie es nicht hätten, könnte ein zweiter Thread in den synchronisierten Block gelangen, nachdem der erste ihn auf null gesetzt hat, und Ihr lokaler Cache würde immer noch denken, dass er null ist.
Der erste dient nicht der Korrektheit (wenn Sie richtig wären, würde er sich selbst besiegen), sondern der Optimierung.
quelle
Das Deklarieren der Variablen als
volatile
garantiert, dass alle Zugriffe darauf tatsächlich ihren aktuellen Wert aus dem Speicher lesen.Ohne
volatile
kann der Compiler die Speicherzugriffe auf die Variable optimieren (z. B. den Wert in einem Register behalten), sodass nur die erste Verwendung der Variablen den tatsächlichen Speicherort liest, an dem sich die Variable befindet. Dies ist ein Problem, wenn die Variable zwischen dem ersten und dem zweiten Zugriff von einem anderen Thread geändert wird. Der erste Thread hat nur eine Kopie des ersten (vormodifizierten) Werts, daherif
testet die zweite Anweisung eine veraltete Kopie des Wertes der Variablen.quelle
volatile
. B. mithilfe von . Register haben nichts mit unvollständigen Objekten und DCL-Problemen zu tun.volatile
ist zu eng - wenn das alles flüchtig gewesen wäre, hätte das doppelt überprüfte Sperren in <Java5 gut funktioniert.volatile
führt eine Speicherbarriere ein, die eine bestimmte Neuordnung illegal macht - ohne diese wäre es immer noch unsicher, selbst wenn wir niemals veraltete Werte aus dem Speicher lesen. Edit: alf hat mich geschlagen, hätte mir keinen schönen Teevolatlie
Verweis auf einen Singleton Ihren Thread dazu führt, den Hauptspeicher erneut zu lesen - aber es ist ein sekundärer Effekt , nicht die Ursache eines Problems :))Ein flüchtiger Lesevorgang ist an sich nicht wirklich teuer.
Sie können einen Test entwerfen, der
getInstance()
in einer engen Schleife aufgerufen wird, um die Auswirkungen eines flüchtigen Lesevorgangs zu beobachten. Dieser Test ist jedoch nicht realistisch. In einer solchen Situation ruft der Programmierer normalerweisegetInstance()
einmal auf und speichert die Instanz für die Dauer der Verwendung zwischen.Ein weiteres Impl ist die Verwendung eines
final
Feldes (siehe Wikipedia). Dies erfordert einen zusätzlichen Lesevorgang, der teurer werden kann als dievolatile
Version. Diefinal
Version kann in einer engen Schleife schneller sein, jedoch ist dieser Test wie zuvor argumentiert umstritten.quelle
Das doppelt überprüfte Sperren ist eine Technik, die verhindert, dass eine weitere Instanz von Singleton erstellt wird, wenn die
getInstance
Methode in einer Multithreading-Umgebung aufgerufen wird.Passt auf
volatile
Schlüsselwort in der Deklaration des Instanzmitglieds. Dadurch wird der Compiler angewiesen, immer aus dem Hauptspeicher und nicht aus dem CPU-Cache zu lesen und in diesen zu schreiben. Bei dervolatile
Variablengarantie, die vor der Beziehung erfolgt, erfolgt der gesamte Schreibvorgang vor dem Lesen der Instanzvariablen.Nachteile
volatile
Schlüsselwort ordnungsgemäß funktionieren muss, ist es nicht mit Java 1.4 und niedrigeren Versionen kompatibel. Das Problem besteht darin, dass bei einem Schreibvorgang außerhalb der Reihenfolge möglicherweise die Instanzreferenz zurückgegeben werden kann, bevor der Singleton-Konstruktor ausgeführt wird.Es gibt mehrere Realisierungen von Singleton-Mustern mit jeweils Vor- und Nachteilen.
Detaillierte Beschreibung Jeder von ihnen ist zu ausführlich, deshalb habe ich nur einen Link zu einem guten Artikel eingefügt - Alles, was Sie über Singleton wissen wollen
quelle