Verhalten der memory-Barriere in Java
Nach dem Lesen mehr blogs/Artikel etc, ich bin jetzt wirklich verwirrt über das Verhalten von load/store-vor - /nach-memory-Barriere.
Folgenden 2 Zitate von Doug Lea in einem seiner Klarstellung Artikel über JMM, die sind beide sehr unkompliziert:
- Alles, was sichtbar war, zu thread A, wenn er schreibt, um flüchtige Feld f wird sichtbar, thread B, wenn es liest f.
- Beachten Sie, dass es wichtig ist, für beide threads auf dieselbe volatile-variable, um richtig einrichten des happens-before-Beziehung. Es ist nicht so, dass alles sichtbar thread Ein, wenn es schreibt flüchtigen Feld f wird sichtbar, thread B nach der es liest flüchtig Feld g.
Aber dann, als schaute ich in eine andere blog über die memory-Barriere, habe ich diese:
- Einer store-barrier, "sfence" - Befehl auf x86 -, zwingt alle store-Anweisungen vor, um die Barriere zu passieren, bevor die Barriere und haben die store-buffer geleert-cache für die CPU, auf die Sie ausgestellt wird.
- Eine Last barrier, "lfence" - Befehl auf x86 -, erzwingt Sie, dass alle load-Anweisungen nachdem die Barriere zu passieren, nach der Schranke und warten dann auf die load-Puffer zu entleeren für die CPU.
Mir, Doug Lea ' s Klarstellung ist strenger als die andere: im Grunde bedeutet es, wenn der laden Barriere-und store-barrier werden auf verschiedenen Monitoren, kann die Konsistenz der Daten nicht gewährleistet werden. Aber später bedeutet, selbst wenn die Barrieren auf verschiedenen Monitoren, kann die Konsistenz der Daten gewährleistet werden. Ich bin nicht sicher, ob ich das Verständnis dieser 2 richtig und auch ich bin mir nicht sicher, welche von Ihnen richtig ist.
Unter Berücksichtigung der folgenden codes:
public class MemoryBarrier {
volatile int i = 1, j = 2;
int x;
public void write() {
x = 14; //W01
i = 3; //W02
}
public void read1() {
if (i == 3) { //R11
if (x == 14) //R12
System.out.println("Foo");
else
System.out.println("Bar");
}
}
public void read2() {
if (j == 2) { //R21
if (x == 14) //R22
System.out.println("Foo");
else
System.out.println("Bar");
}
}
}
Sagen wir, wir haben 1 schreiben thread TW1 ersten Aufruf der MemoryBarrier die write () - Methode, dann haben wir 2 Leser-threads TR1 und TR2 nennen MemoryBarrier ist read1() und read2 () - Methode.Betrachten Sie dieses Programm ausführen auf der CPU, die nicht erhalten Bestellung (x86 beibehalten der Bestellung für solche Fälle was nicht der Fall ist), je nach Speicher-Modell, wird es eine StoreStore Barriere (sagen wir SB1) zwischen W01/W02, sowie 2 LoadLoad Barriere zwischen R11/R12 und R21/R22 (sagen wir mal, RB1 und RB2).
- Seit SB1 und RB1 sind am gleichen monitor ich, also thread TR1, welche Anrufe read1 sollte immer sehen, 14 x, auch "Foo" wird immer gedruckt.
- SB1 und RB2 sind auf verschiedenen Monitoren, wenn Doug Lea ist richtig, thread TR2 werden nicht garantiert, um zu sehen, 14 x, was bedeutet, "Bar" gedruckt werden kann gelegentlich. Aber wenn die memory-Barriere läuft wie Martin Thompson beschrieben in der blog, den laden Sperre schieben Sie alle Daten im Hauptspeicher und Last-Schranke ziehen alle Daten vom Hauptspeicher zum cache/Puffer, dann TR2 wird auch garantiert werden, um zu sehen, 14 auf x.
Ich bin mir nicht sicher, welches der richtige ist, oder beide von Ihnen, aber was Martin Thompson beschrieben, ist nur für die x86-Architektur. JMM übernimmt keine Garantie änderung x der sichtbar ist, TR2, aber x86-Implementierung unterstützt.
Dank~
- Man sollte sich keine Gedanken über die memory-Barrieren auf x86. Die Semantik von Java und das Java-Memory-Modell definiert sind, auf eine abstrakte Maschine. Das ist das einzige, was zählt. Die Java-Laufzeitumgebung sorgt dafür, dass die Garantien durch die abstrakte Maschine erfüllt sind, während der Laufzeit.
- Als eine Angelegenheit von der Tat die x86-Semantik (einschließlich der cache-Kohärenz) sind stärker als das, was die jmm Anforderungen, aber es gibt keinen Grund für Sie interessiert, wenn Sie nicht arbeiten auf einer java-Laufzeitumgebung für x86 als nosid richtig Punkte raus.
- Ihr Anliegen ist gültig. Es ist möglich // die reader-2 Bar Druck. Jedoch, wenn der Leser threads hatte vorher in Wechselwirkung mit der memory-Barriere-Klasse zwischengespeichert und der Wert von x, reader 2 print foo, weil es auf x für die erste Zeit. Die Interaktion mit schreiben bedeutet die änderung für x sichtbar. Vielleicht ein interessanter test ist read1 und read2 führen sowohl vor und nach W1.
CountDownLatch
führt eine weitere Synchronisierung. Also, wenn SieCountDownLatch
sicher zu stellen, dassread2
ausgeführt wird, nachdemwrite
, dannread2
immer drucken"Foo"
.- Die Antwort ist:
"Bar"
ist natürlich möglich, wenn es keine Synchronisation zwischenwrite
undread2
, und es ist nicht möglich, wenn es eine Synchronisation. Ich denke, das ist nicht das, was Sie interessiert. Also, bitte mehr Informationen liefern, was Sie wirklich wissen wollen. - Was Martin Thompson beschrieben in dem blog sind etwas anders mit Doug Lea ' s Klärung sowie JMM passiert-grundsätzlich vor, die ich ein wenig verwirrt bin. Ich denke, es ist nur für x86-Implementierung, nicht eine JMM Sache. BTW. sehr gute Fänge an CountDownLatch.
- Ich glaube nicht, dass
sfence
leert die Speicher-Puffer-cache zu. Peterson/Decker Synchronisation mussmfence
.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Doug Lea hat Recht. Sie finden den entsprechenden Teil im Abschnitt §17.4.4 der Java Language Specification:
Speicher-Modell des konkreten Maschine spielt keine Rolle, da die Semantik der Java-Programmiersprache definiert in Bezug auf eine abstrakte Maschine -- unabhängig von der konkreten Maschine. Es liegt in der Verantwortung des Java runtime environment zum ausführen der code in einer solchen Weise, dass es im Einklang mit den Garantien des Java Language Specification.
In Bezug auf die eigentliche Frage:
read2
drucken können"Bar"
, weilread2
können ausgeführt werden, bevorwrite
.CountDownLatch
um sicherzustellen, dassread2
ausgeführt wird nachwrite
, dann Methoderead2
nie drucken"Bar"
, da die Synchronisation mitCountDownLatch
entfernt die Daten Rennen aufx
.Unabhängige volatile-Variablen:
Macht es Sinn, dass ein schreiben einer volatile-Variablen nicht synchronisieren-mit Lesen der andere flüchtige variable?
Ja, es macht Sinn. Wenn zwei threads müssen miteinander interagieren, Sie haben in der Regel verwenden die gleichen
volatile
Variablen, um Informationen auszutauschen. Auf der anderen Seite, wenn ein thread verwendet eine volatile variable, ohne die Notwendigkeit für die Interaktion mit allen anderen threads, die wir nicht bezahlen wollen, die Kosten für ein memory-Barriere.Es ist tatsächlich wichtig in der Praxis. Machen wir ein Beispiel. Die folgende Klasse verwendet eine flüchtige member-variable:
Vorstellen, wie diese Klasse verwendet wird nur lokal innerhalb einer Methode. Der JIT-compiler kann leicht erkennen, dass das Objekt nur innerhalb dieser Methode (Escape-Analyse).
Mit der oben genannten Regel, den JIT-compiler können entfernen Sie alle Effekte des
volatile
liest und schreibt, weil dievolatile
variable kann nicht sein, Zugriffe von einem anderen thread.Diese Optimierung tatsächlich existiert in der Java-JIT-compiler:
write
passiertread2
zeitlich mit keine zusätzliche Synchronisierung, könnte"Bar"
noch gedruckt werden?14
gelesen werden könnenx
nur nachdem (ich meine, passiert-nach es war dort geschrieben, inwrite
, also nurFoo
gedruckt werden.j
, nichti
, also IMO das Zitat1.
nicht gelten.read2
geschehen chronologisch nachwrite
?FileOuputStream
- und write-Zeitstempel zu es von jedem thread. (Ich glaube nicht, das wäre keine zusätzliche Synchronisation, wenn das, was Sie immer waren, an.)write
passiert, bevorread2
. Auch in diesem Fall, die Methoderead2
könnte noch drucken"Bar"
.j
wird überprüft, indem dieread2
thread, eher als die anderenvolatile i
es nicht sehen, die änderung zux
?Soweit ich verstanden die Frage ist eigentlich an flüchtigen lese - /Schreibvorgänge und seiner geschieht-vor garantiert. Apropos, das Teil habe ich nur eine Sache hinzufügen nosid Antwort:
Flüchtigen schreibt kann nicht verschoben werden, bevor Sie normal schreibt, flüchtig liest, kann nicht verschoben werden, nach der normalen liest. Das ist, warum
read1()
undread2()
Ergebnisse werden als nosid schrieb.Sprechen über Hindernisse - die defininition klingt gut für mich, aber die eine Sache, wohl verwirrt Sie ist, dass diese Dinge/tools/Weise/Mechanismus (Nenn es wie du willst) zu implementieren beschriebene Verhalten in JMM im hotspot. Bei der Verwendung von Java, sollten Sie sich auf JMM garantiert, keine Implementierungsdetails.