Warum stellt volatile in Java 5+ keine Sichtbarkeit von einem anderen Thread sicher?

Lesezeit: 8 Minuten

Benutzer-Avatar
Oleg

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?

  • @OliCharlesworth Ich denke, er fragt, warum die verschiedenen zwischengespeicherten Werte von b sind nicht synchronisiert b=1 nach dem flüchtigen Schreiben/Lesen auf a.

    – 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

Benutzer-Avatar
John Vint

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 ttoder 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.

  • Aber das OP sagt, dass dies nicht das Verhalten ist, das er beobachtet.

    – Oliver Charlesworth

    16. Mai 2012 um 15:08 Uhr

  • @OliCharlesworth Dann muss er sich auf einer Java-Laufzeitumgebung befinden, die nicht dem Java 5-Speichermodell entspricht, oder etwas falsch machen. Ich werde das selbst testen und sehen, ob ich die gleiche Interaktion beobachte, ich habe das Gefühl, dass ich es nicht tun werde

    – John Vint

    16. Mai 2012 um 15:10 Uhr

  • Wird das b == 0 als JIT-Optimierung entfernt?

    – John Vint

    16. Mai 2012 um 15:20 Uhr

  • Ich habe -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintAssembly verwendet, das vom Eclipse-Debug ausgeführt wird (aber auch mit normaler Ausführung) mit JDK6u30 32-Bit (auf 64-Bit-Computer).

    – Walter Laan

    16. Mai 2012 um 15:46 Uhr

  • Ich wette, es ist ein OSR-Fehler, es ist nicht der erste OSR-Fehler, der auftritt (fav. inkl. TieredCompilation mit c1->c2 und einem JVM-Absturz).

    – bestes

    16. Mai 2012 um 22:32 Uhr

Benutzer-Avatar
Assyrien

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.

  • Ich würde dem +1 geben, weil ich denke, dass wir deshalb niemals “Fehler” sehen könnten … außer dass einige Leute berichten, dass sie “Fehler” sehen, also muss das irgendwie nicht zutreffen!

    – yshavit

    16. Mai 2012 um 15:00 Uhr

  • Für die Leute, die diesen Fehler nie sehen … nur um die Dinge einzugrenzen … auf welcher JVM und CPU testen Sie?

    – chao

    16. Mai 2012 um 15:40 Uhr


  • @JohnVint Hölle ja. Mit -d32 es zuverlässig reproduziert das Problem.

    – Marko Topolnik

    16. Mai 2012 um 18:55 Uhr

  • @MarkoTopolnik Wenn ich mir die Assembly ansehe, scheint es (obwohl ich mich irren kann), dass es, wenn es fehlschlägt, auf den lokalen Speicher von verweist tt. Wobei ‘b’==0 wahr ist.

    – John Vint

    16. Mai 2012 um 18:57 Uhr

  • @JohnVint Ich schaue in diesen Thread hinein [concurrency-interest], es ist nur der 32-Bit-Client, der Probleme verursacht. Ich habe auf meinem Computer überprüft, mit -server sehe ich den Effekt nicht.

    – Marko Topolnik

    16. Mai 2012 um 20:14 Uhr


Vielleicht möchten Sie einen Diskussionsthread auf der Mailingliste für Parallelinteressen zu dieser Frage lesen: http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html

Es scheint, als ob das Problem mit der Client-JVM (-client) einfacher reproduziert werden kann.

Benutzer-Avatar
Ofek Ron

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;
        }  
    }
}

1149420cookie-checkWarum stellt volatile in Java 5+ keine Sichtbarkeit von einem anderen Thread sicher?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy