Wie bekannt ist, garantieren wir, dass wenn wir eine Objektreferenz haben und diese Referenz ein letztes Feld hat - wir sehen alle erreichbaren Felder vom letzten Feld (zumindest wenn der Konstruktor fertig war)
Wie ich in diesem Fall gesagt habe, haben wir die Garantie, dass bar()
method immer object
ausgibt, weil:
1. Ich habe den vollständigen Code der Klasse Foo
aufgelistet und die Karte ist final;
2. Wenn ein Thread den Verweis auf Foo
und diesen Verweis! = Null sehen wird, haben wir Garantien, dass reachable vom letzten map
-Referenzwert tatsächlich sein wird.
denke ich auch das
Hier haben wir die gleichen Garantien über bar()
Methode, aber bar2
kann NullPointerException
werfen, obwohl nonFinalMap
Zuweisung vor map
Zuweisung auftritt.
Ich möchte wissen, wie flüchtig:
Wie ich weiß bar()
method kann NullPoinerException
nicht ausgeben, aber es kann null
; (Ich bin mir über diesen Aspekt nicht ganz sicher)
Ich denke, hier haben wir die gleichen Garantien über bar()
method auch bar2()
kann NullPointerException
nicht werfen, weil nonVolatileMap
Zuweisung eine höhere flüchtige Zuordnungszuweisung geschrieben hat, aber es kann null ausgeben
Hinzugefügt nach Elliott Frisch Kommentar
Veröffentlichung durch Rennbeispiel:
%Vor%Bitte überprüfen oder korrigieren Sie meine Kommentare zu Code-Snippets.
Im Bereich des aktuellen Java-Speichermodells ist volatile
nicht gleich final
. Mit anderen Worten: Sie können final
nicht durch% co_de ersetzen % und denke, dass die Garantien für eine sichere Konstruktion gleich sind. Vor allem kann dies theoretisch passieren:
Das Schreiben des Feldes volatile
im Konstruktor ist also nicht so sicher.
Intuition: Im obigen Beispiel gibt es ein Rennen auf volatile
. Dieses Rennen wird nicht durch das Feld m
M.x
eliminiert, nur die volatile
selbst m
würde helfen. Mit anderen Worten: volatile
modifier in diesem Beispiel ist am falschen Platz , um nützlich zu sein. In der sicheren Veröffentlichung müssen Sie "schreiben - & gt; flüchtiges Schreiben - & gt; flüchtiges Lesen, das volatiles Schreiben beobachtet - & gt; liest (jetzt beobachtet Schreiben vor dem volatilen Schreiben)", und stattdessen haben Sie "flüchtiges Schreiben - & gt; schreiben - & gt; lesen - & gt; flüchtiges Lesen (das nicht den flüchtigen Schreibvorgang beobachtet) ".
Trivia 1: Diese Eigenschaft bedeutet, dass wir volatile
-s in Konstruktoren viel aggressiver optimieren können. Dies bestätigt die Intuition, dass unbeobachtetes flüchtiges Material (und tatsächlich wird es nicht beobachtet werden, bis Konstruktor mit nicht-entweichendem volatile
endet) entspannt werden kann.
Trivia 2: Dies bedeutet auch, dass Sie this
Variablen nicht sicher initialisieren können. Ersetzen Sie volatile
durch M
im obigen Beispiel, und Sie haben ein eigenartiges reales Verhalten! Rufen Sie AtomicInteger
in einem Thread auf, veröffentlichen Sie die Instanz unsicher und führen Sie new AtomicInteger(42)
in einem anderen Thread aus - sehen Sie get()
? JMM sagt, wie gesagt, "nein". Neuere Überarbeitungen des Java-Speichermodells versuchen, eine sichere Konstruktion für alle Initialisierungen zu gewährleisten, um diesen Fall zu erfassen. Und viele Nicht-x86-Ports, auf die es ankommt haben dies bereits verstärkt , um sicher zu sein.
Trivia 3: Doug Lea : "Dieses 42
vs final
Problem hat zu einigen twisty Konstruktionen in java.util.concurrent geführt, um 0 als Basis / Default-Wert in Fällen zuzulassen, in denen es nicht natürlich sein würde. Diese Regel ist saugt und sollte geändert werden."
Das heißt, das Beispiel kann listiger gemacht werden:
%Vor% Wenn transitiv gelesen wird volatile
field nachdem volatile read den von volatile write im Konstruktor geschriebenen Wert beobachtet hat, treten die üblichen sicheren Veröffentlichungsregeln ein.
Tags und Links java concurrency volatile visibility happens-before