Entsprechend:
http://www.ibm.com/developerworks/library/j-jtp03304/
Wenn unter dem neuen Speichermodell Thread A in eine flüchtige Variable V schreibt und Thread B aus V liest, sind alle Variablenwerte, die zum Zeitpunkt des Schreibens von V für A sichtbar waren, jetzt garantiert für B sichtbar
Und viele Stellen im Internet geben an, dass der folgende Code niemals “Fehler” ausgeben sollte:
public class Test {
volatile static private int a;
static private int b;
public static void main(String [] args) throws Exception {
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
int tt = b; // makes the jvm cache the value of b
while (a==0) {
}
if (b == 0) {
System.out.println("error");
}
}
}.start();
}
b = 1;
a = 1;
}
}
b
sollte 1 für alle Threads sein, wenn a
ist 1.
Jedoch Ich bekomme manchmal “error” gedruckt. Wie ist das möglich?
Aktualisieren:
Für alle Interessierten wurde dieser Fehler für Java 7u6 Build b14 adressiert und behoben. Sie können den Fehlerbericht / die Fehlerbehebungen hier einsehen
Ursprüngliche Antwort
Wenn Sie in Bezug auf die Sichtbarkeit/Ordnung von Erinnerungen denken, müssen Sie über ihre Vorher-Beziehung nachdenken. Die wichtige Voraussetzung für b != 0
ist für a == 1
. Wenn a != 1
dann kann b entweder 0 oder 1 sein.
Sobald ein Thread sieht a == 1
dann ist dieser Thread garantiert zu sehen b == 1
.
Post Java 5, im OP-Beispiel einmal die while(a == 0)
bricht b aus, ist garantiert 1
Bearbeiten:
Ich habe die Simulation viele Male ausgeführt und Ihre Ausgabe nicht gesehen.
Unter welchem Betriebssystem, Java-Version und CPU testen Sie?
Ich verwende Windows 7, Java 1.6_24 (versuche es mit _31)
Bearbeiten 2:
Ein großes Lob an das OP und Walter Laan – Bei mir ist es nur passiert, als ich von 64-Bit-Java auf 32-Bit-Java auf (aber nicht ausgeschlossen) einem 64-Bit-Windows 7 umgestiegen bin.
Bearbeiten 3:
Der Auftrag an tt
oder besser gesagt das statische Get von b
scheint einen erheblichen Einfluss zu haben (um dies zu beweisen, entfernen Sie die int tt = b;
und es sollte immer funktionieren.
Es erscheint die Last von b
hinein tt
speichert das Feld lokal, das dann in der if coniditonal verwendet wird (die Referenz auf diesen Wert nicht tt
). Also wenn b == 0
Richtig ist es wohl, dass der örtliche Laden dazugehört tt
war 0 (zu diesem Zeitpunkt ist es ein Rennen, 1 dem lokalen zuzuweisen tt
). Dies scheint nur für 32-Bit-Java 1.6 & 7 mit Client-Set zuzutreffen.
Ich habe die beiden Ausgangsbaugruppen verglichen und der unmittelbare Unterschied war hier. (Beachten Sie, dass dies Ausschnitte sind).
Dieser gedruckte “Fehler”
0x021dd753: test %eax,0x180100 ; {poll}
0x021dd759: cmp $0x0,%ecx
0x021dd75c: je 0x021dd748 ;*ifeq
; - Test$1::run@7 (line 13)
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test$1::run@13 (line 17)
0x021dd767: nop
0x021dd768: jmp 0x021dd7b8 ; {no_reloc}
0x021dd76d: xchg %ax,%ax
0x021dd770: jmp 0x021dd7d2 ; implicit exception: dispatches to 0x021dd7c2
0x021dd775: nop ;*getstatic out
; - Test$1::run@16 (line 18)
0x021dd776: cmp (%ecx),%eax ; implicit exception: dispatches to 0x021dd7dc
0x021dd778: mov $0x39239500,%edx ;*invokevirtual println
Und
Dies hat “Fehler” nicht gedruckt
0x0226d763: test %eax,0x180100 ; {poll}
0x0226d769: cmp $0x0,%edx
0x0226d76c: je 0x0226d758 ;*ifeq
; - Test$1::run@7 (line 13)
0x0226d76e: mov $0x341b77f8,%edx ; {oop('Test')}
0x0226d773: mov 0x154(%edx),%edx ;*getstatic b
; - Test::access$0@0 (line 3)
; - Test$1::run@10 (line 17)
0x0226d779: cmp $0x0,%edx
0x0226d77c: jne 0x0226d7a8 ;*ifne
; - Test$1::run@13 (line 17)
0x0226d782: nopw 0x0(%eax,%eax,1)
0x0226d788: jmp 0x0226d7ed ; {no_reloc}
0x0226d78d: xchg %ax,%ax
0x0226d790: jmp 0x0226d807 ; implicit exception: dispatches to 0x0226d7f7
0x0226d795: nop ;*getstatic out
; - Test$1::run@16 (line 18)
0x0226d796: cmp (%ecx),%eax ; implicit exception: dispatches to 0x0226d811
0x0226d798: mov $0x39239500,%edx ;*invokevirtual println
In diesem Beispiel stammt der erste Eintrag von einem Durchlauf, der “Fehler” ausgab, während der zweite von einem stammt, der dies nicht tat.
Es scheint, dass der Arbeitslauf geladen und zugewiesen wurde b
richtig vor dem Testen gleich 0.
0x0226d76e: mov $0x341b77f8,%edx ; {oop('Test')}
0x0226d773: mov 0x154(%edx),%edx ;*getstatic b
; - Test::access$0@0 (line 3)
; - Test$1::run@10 (line 17)
0x0226d779: cmp $0x0,%edx
0x0226d77c: jne 0x0226d7a8 ;*ifne
; - Test$1::run@13 (line 17)
Während der Lauf, der “Fehler” ausgegeben hat, die zwischengespeicherte Version von geladen hat %edx
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test$1::run@13 (line 17)
Für diejenigen, die mehr Erfahrung mit Assembler haben, wiegen Sie sich bitte ein 🙂
Bearbeiten 4
Sollte meine letzte Bearbeitung sein, da die Concurrency-Entwickler es in die Hand bekommen, habe ich mit und ohne getestet
int tt = b;
Aufgabe etwas mehr. Ich habe festgestellt, dass, wenn ich das Maximum von 100 auf 1000 erhöhe, eine Fehlerquote von 100% zu bestehen scheint, wenn int tt = b
eingeschlossen ist, und eine Wahrscheinlichkeit von 0 %, wenn sie ausgeschlossen ist.
Basierend auf dem Auszug aus JCiP unten hätte ich gedacht, dass Ihr Beispiel niemals “Fehler” drucken sollte:
Die Sichtbarkeitseffekte flüchtiger Variablen gehen über den Wert der flüchtigen Variablen selbst hinaus. Wenn ein Faden EIN schreibt in eine flüchtige Variable und anschließend in einen Thread B liest dieselbe Variable, die Werte von alle Variablen, die sichtbar waren EIN vor dem Schreiben in die flüchtige Variable sichtbar werden B nach dem Lesen der flüchtigen Variablen.
Meiner Meinung nach ist das Problem auf Mangel an entstanden Synchronisation :
NOTIZ : Wenn b = 1 vor a = 1 auftritt und a flüchtig ist, während b dies nicht ist, wird b = 1 tatsächlich für alle Threads nur aktualisiert, nachdem a = 1 beendet ist (gemäß der Quate-Logik).
Was in Ihrem Code passiert, ist, dass b = 1 zuerst nur für den Hauptprozess aktualisiert wurde und erst dann, wenn die flüchtige Zuweisung abgeschlossen war, alle Threads b aktualisiert wurden. Ich denke, dass Zuweisungen von flüchtigen Elementen möglicherweise nicht als atomare Operationen funktionieren (muss weit zeigen und den Rest der Referenzen irgendwie aktualisieren, um sich wie flüchtige Elemente zu verhalten), also wäre dies meine Vermutung, warum ein Thread b = 0 anstelle von b = 1 gelesen hat.
Betrachten Sie diese Änderung am Code, die meine Behauptung zeigt:
public class Test {
volatile static private int a;
static private int b;
private static Object lock = new Object();
public static void main(String [] args) throws Exception {
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
int tt = b; // makes the jvm cache the value of b
while (true) {
synchronized (lock ) {
if (a!=0) break;
}
}
if (b == 0) {
System.out.println("error");
}
}
}.start();
}
b = 1;
synchronized (lock ) {
a = 1;
}
}
}
@OliCharlesworth Ich denke, er fragt, warum die verschiedenen zwischengespeicherten Werte von
b
sind nicht synchronisiertb=1
nach dem flüchtigen Schreiben/Lesen aufa
.– yshavit
16. Mai 2012 um 14:35 Uhr
Haben Sie diesen Code tatsächlich ausgeführt und gesehen, dass “Fehler” mit Java 1.5+ gedruckt wurde?
– Assylias
16. Mai 2012 um 14:37 Uhr
@OfekRon Nach dem Java-Speichermodell spielt es keine Rolle, ob
b
flüchtig ist oder nicht, weil auf das Schreiben darauf ein Schreiben in eine flüchtige Variable folgt und in dem anderen Thread dem Lesen davon das Lesen derselben flüchtigen Variablen vorausgeht.– Marko Topolnik
16. Mai 2012 um 17:35 Uhr
Dieser Thread wird jetzt in der Java Concurrency-Interest E-Mail-Liste diskutiert: cs.oswego.edu/pipermail/concurrency-interest/2012-May/…
– yshavit
16. Mai 2012 um 17:51 Uhr
Nur ein kurzes Update von der Concurrency-Interest-Liste, es sieht so aus, als wäre dies in der neuesten Java7 behoben: download.java.net/jdk7u6/changes/jdk7u6-b14.html (Sehen Sie sich den letzten Eintrag im Hotspot-Bereich an. Die Fehler-ID verweist auf einen Fehlerbericht mit Ihrem Anwendungsfall.
– yshavit
15. Juni 2012 um 17:20 Uhr