Gemäß:
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, ist garantiert, dass alle Variablenwerte, die zum Zeitpunkt des Schreibens von V für A sichtbar waren, jetzt für B sichtbar sind
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 für alle Threads a
1 sein, wenn 1 ist.
Allerdings bekomme ich manchmal "Fehler" gedruckt . Wie ist das möglich?
b
nicht synchronisiert werden .b=1
a
b
es flüchtig ist oder nicht, da auf das Schreiben in ein flüchtiges var ein Schreiben folgt und im anderen Thread dem Lesen desselben das Lesen desselben vorausgeht flüchtige var.Antworten:
Aktualisieren:
Für alle Interessierten wurde dieser Fehler behoben und für Java 7u6 Build B14 behoben. Sie können den Fehlerbericht / die Fehlerbehebungen hier sehen
Ursprüngliche Antwort
Wenn Sie in Bezug auf die Sichtbarkeit / Reihenfolge des Gedächtnisses denken, müssen Sie über die Beziehung nachdenken, die vor dem Ereignis besteht. Die wichtige Voraussetzung für
b != 0
ist füra == 1
. Wenna != 1
dann kann b entweder 0 oder 1 sein.Sobald ein Thread sieht,
a == 1
wird dieser Thread garantiert sehenb == 1
.Post Java 5, im OP-Beispiel, sobald der
while(a == 0)
Ausbruch b garantiert 1 istBearbeiten:
Ich habe die Simulation viele Male ausgeführt und Ihre Ausgabe nicht gesehen.
Unter welchem Betriebssystem, welcher Java-Version und welcher CPU testen Sie?
Ich bin unter Windows 7, Java 1.6_24 (versuche es mit _31)
Bearbeiten 2:
Ein großes Lob an das OP und Walter Laan - Für mich geschah dies nur, als ich von 64-Bit-Java auf 32-Bit-Java auf einem 64-Bit-Windows 7 umstieg (aber nicht ausgeschlossen werden darf).
Edit 3:
Die Zuordnung zu
tt
oder vielmehr das Statikget vonb
scheint einen signifikanten Einfluss zu haben (um dies zu beweisen, entfernen Sie dasint tt = b;
und es sollte immer funktionieren.Es scheint, dass das Laden von
b
intt
das Feld lokal speichert, das dann im if-Koniditon verwendet wird (der Verweis auf diesen Wert nichttt
). Wenn diesb == 0
zutrifft, bedeutet dies wahrscheinlich, dass der lokale Speichertt
0 war (zu diesem Zeitpunkt ist es ein Rennen , dem lokalen Speicher 1 zuzuweisentt
). Dies scheint nur für 32-Bit-Java 1.6 & 7 mit festgelegtem Client zu gelten.Ich habe die beiden Ausgangsbaugruppen verglichen und der unmittelbare Unterschied war hier. (Denken Sie daran, dies sind Ausschnitte).
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 druckte nicht "Fehler"
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 aus einem Lauf, der "Fehler" druckte, während der zweite aus einem Lauf stammte, der dies nicht tat.
Es scheint, dass der Arbeitslauf
b
vor dem Testen korrekt geladen und zugewiesen wurde und gleich 0 ist.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 des Laufs, der "Fehler" druckte, wurde die zwischengespeicherte Version von geladen
%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 bitte :)
Bearbeiten 4
Sollte meine letzte Bearbeitung sein, da die Parallelitätsentwickler eine Hand darauf bekommen, habe ich mit und ohne die
int tt = b;
Aufgabe noch etwas mehr getestet . Ich fand heraus, dass wenn ich das Maximum von 100 auf 1000 erhöhe, es eine 100% ige Fehlerrate zu geben scheint, wennint tt = b
es eingeschlossen ist, und eine 0% ige Chance, wenn es ausgeschlossen ist.quelle
Basierend auf dem folgenden Auszug aus JCiP hätte ich gedacht, dass Ihr Beispiel niemals "Fehler" drucken sollte:
quelle
-d32
es zuverlässig das Problem.tt
. In dem 'b' == 0 ist wahr.Vielleicht möchten Sie einen Diskussionsthread in der Mailingliste für Parallelitätsinteressen zu dieser Frage lesen : http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html
Es scheint, dass das Problem mit der Client-JVM (-client) leichter reproduziert werden kann.
quelle
Meiner Meinung nach ist das Problem aufgrund mangelnder Synchronisation aufgetreten :
BEACHTEN : Wenn b = 1 vor a = 1 auftritt und a flüchtig ist, während b nicht ist, wird b = 1 tatsächlich erst nach Abschluss von a = 1 für alle Threads aktualisiert (gemäß der Logik des Quate).
Was in Ihrem Code steht, ist, dass b = 1 zuerst nur für den Hauptprozess aktualisiert wurde, dann erst, wenn die flüchtige Zuweisung abgeschlossen ist, alle Threads b aktualisiert wurden. Ich denke, dass Zuweisungen von flüchtigen Stoffen möglicherweise nicht als atomare Operationen funktionieren (muss weit zeigen und den Rest der Referenzen irgendwie aktualisieren, um sich wie flüchtige Stoffe zu verhalten), daher wäre dies meine Vermutung, warum ein Thread b = 0 anstelle von b = 1 liest.
Betrachten Sie diese Änderung des Codes, 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; } } }
quelle
Can Reorder
Raster feststellen, welche Synchronisation das JMM verspricht. Die wichtigen Teile sind: 1. Normaler Speicher kann nicht mit einem nachfolgenden flüchtigen Speicher neu angeordnet werden, und 2. Flüchtige Ladung kann nicht mit einem nachfolgenden normalen Laden neu angeordnet werden. Beide dieses Beispiel erklärt