Warum macht das Markieren einer Java-Variablen volatile Dinge weniger synchron?

8

Also habe ich gerade über das flüchtige Schlüsselwort erfahren, während ich einige Beispiele für einen Abschnitt geschrieben habe, den ich morgen TAE bin. Ich habe ein schnelles Programm geschrieben, um zu demonstrieren, dass die Operationen ++ und - nicht atomar sind.

%Vor%

Wie erwartet, ist die Ausgabe dieses Programms im Allgemeinen wie folgt:

%Vor%

Allerdings, wenn ich ändere:

%Vor%

bis

%Vor%

mein Ausgang ändert sich zu:

%Vor%

Ich habe gelesen Wann genau verwenden Sie das flüchtige Schlüsselwort in Java? , also habe ich das Gefühl, ein grundlegendes Verständnis davon zu haben, was das Schlüsselwort tut (Aufrechterhaltung der Synchronisation über zwischengespeicherte Kopien einer Variablen in verschiedenen Threads, aber nicht Lesen-Aktualisieren-Schreiben-Sicher). Ich verstehe, dass dieser Code natürlich nicht Thread-sicher ist. Es ist insbesondere nicht threadsicher, als Beispiel für meine Schüler zu fungieren. Ich bin jedoch neugierig, warum das Hinzufügen des Schlüsselwortes volatile die Ausgabe nicht so "stabil" macht, wie wenn das Schlüsselwort nicht vorhanden ist.

    
JohnS 09.11.2010, 04:53
quelle

7 Antworten

1

Der Grund für all diese Nullen ist nicht , dass sich die ++ und - 's gegenseitig ausgleichen. Der Grund ist, dass hier nichts ist damit count in den Schleifen-Threads count im Haupt-Thread beeinflusst. Sie benötigen Synchronisationsblöcke oder eine volatile count (eine "Speicherbarriere"), um die JVM dazu zu zwingen, dass alle den gleichen Wert sehen. Bei Ihrer speziellen JVM / Hardware passiert höchstwahrscheinlich, dass der Wert überhaupt in einem Register gespeichert wird Zeiten und nie zu Cache - geschweige denn Hauptspeicher - überhaupt.

Im zweiten Fall machst du das, was du beabsichtigst: nicht-atomare Inkremente und Dekremente für dasselbe course und Ergebnisse, die so sind, wie du es erwartet hast.

Dies ist eine uralte Frage, aber zu jedem Thread sollte etwas gesagt werden, das seine eigene , unabhängige Kopie der Daten beibehält.

    
RalphChapin 10.10.2013, 18:43
quelle
5

Die Frage "Warum läuft der Code schlechter?" mit dem Schlüsselwort volatile ist keine gültige Frage. Es verhält sich anders aufgrund des unterschiedlichen Speichermodells, das für flüchtige Felder verwendet wird. Die Tatsache, dass die Ausgabe Ihres Programms ohne das Schlüsselwort auf 0 tendiert, kann nicht verlässlich sein. Wenn Sie zu einer anderen Architektur mit unterschiedlichen CPU-Threads oder CPU-Anzahl wechseln, sind sehr unterschiedliche Ergebnisse nicht ungewöhnlich.

Außerdem ist es wichtig, sich daran zu erinnern, dass, obwohl x++ atomar erscheint, es sich tatsächlich um eine Lese- / Modifizierungs- / Schreiboperation handelt. Wenn Sie Ihr Testprogramm auf einer Reihe verschiedener Architekturen ausführen, werden Sie unterschiedliche Ergebnisse finden, da die Implementierung der JVM volatile sehr hardwareabhängig ist. Der Zugriff auf volatile -Felder kann auch wesentlich langsamer sein als der Zugriff auf zwischengespeicherte Felder - manchmal um 1 oder 2 Größenordnungen, was das Timing Ihres Programms verändert.

Die Verwendung des Schlüsselworts volatile richtet eine Speicherbarriere für das spezifische Feld ein und (ab Java 5) wird diese Speicherbarriere auf alle anderen gemeinsam genutzten Variablen erweitert. Dies bedeutet, dass der Wert der Variablen beim Zugriff auf den zentralen Speicher kopiert bzw. aus diesem entfernt wird. Es gibt jedoch feine Unterschiede zwischen volatile und dem Schlüsselwort synchronized in Java. Zum Beispiel gibt es kein Locking mit volatile . Wenn also mehrere Threads eine flüchtige Variable aktualisieren, werden Race-Bedingungen um nicht-atomare Operationen bestehen.

Hier ist eine gute Lektüre zu diesem Thema:

Hoffe, das hilft.

    
Gray 09.11.2010 05:10
quelle
4

Eine fundierte Vermutung zu dem, was Sie sehen - wenn nicht als flüchtig markiert, verwendet der JIT-Compiler die x86 inc / dec-Operationen, die die Variable atomar aktualisieren können. Sobald sie als flüchtig markiert sind, werden diese Operationen nicht länger verwendet und die Variable wird stattdessen gelesen, inkrementiert / dekrementiert und dann schließlich geschrieben, was mehr "Fehler" verursacht.

Das nichtflüchtige Setup hat keine Garantie, dass es gut funktioniert - bei einer anderen Architektur könnte es schlimmer sein, als wenn es als flüchtig markiert ist. Die Markierung des Volatilitätsfeldes löst keine der hier vorliegenden Race-Probleme.

Eine Lösung wäre die Verwendung der AtomicInteger-Klasse, die atomare Inkremente / Dekremente erlaubt.

    
Mania 09.11.2010 05:10
quelle
2

Flüchtige Variablen verhalten sich so, als ob jede Interaktion in einem synchronisierten Block eingeschlossen wäre. Wie Sie erwähnt haben, ist increment und decrement nicht atomar, dh jedes Inkrement und Dekrement enthält zwei synchronisierte Bereiche (den Lese- und den Schreibbereich). Ich vermute, dass das Hinzufügen dieser Pseudolocks die Wahrscheinlichkeit erhöht, dass die Operationen konfligieren.

Im Allgemeinen würden die zwei Threads einen zufälligen Abstand von einem anderen haben, was bedeutet, dass die Wahrscheinlichkeit, dass eine der beiden Threads die andere überschreibt, gerade ist. Aber die von volatile erzwungene Synchronisation zwingt sie dazu, sich im inversen Lockstep zu befinden, was, wenn sie auf die falsche Art ineinandergreifen, die Chance auf ein verpaßtes Inkrementieren oder Dekrementieren erhöht. Wenn sie in diesen Lockstep kommen, macht es die Synchronisation weniger wahrscheinlich, dass sie ausbrechen und die Abweichung erhöhen.

    
Zack Bloom 09.11.2010 05:13
quelle
2

Ich bin über diese Frage gestolpert und nachdem ich mit dem Code für ein bisschen gespielt habe, habe ich eine sehr einfache Antwort gefunden.

Nach dem ersten Aufwärmen und Optimierungen (die ersten beiden Zahlen vor den Nullen) wenn die JVM mit voller Geschwindigkeit arbeitet T1 startet und endet einfach bevor T2 gerade startet, also count geht den ganzen Weg bis 10000 und dann bis 0. Wenn ich die Anzahl der Iterationen in den Worker-Threads von 10000 auf 100000000 änderte, ist die Ausgabe jedes Mal sehr instabil und unterschiedlich.

Der Grund für die unstable Ausgabe beim Hinzufügen von volatile ist, dass es den Code viel langsamer macht und sogar mit 10000 Iterationen T2 genügend Zeit hat um T1 zu starten und zu interferieren.

    
Oleg 15.01.2014 18:16
quelle
1

Wenn Sie einen Wert von count sehen, der nicht ein Vielfaches von 10000 ist, wird nur ein schlechter Optimierer angezeigt.

    
curiousguy 19.10.2011 11:28
quelle
0

Es macht die Dinge nicht weniger synchronisiert. Dadurch werden sie mehr synchronisiert, da Threads immer einen aktuellen Wert für die Variable "sehen". Dies erfordert die Errichtung von Speicherbarrieren, die zeitaufwendig sind.

    
EJP 09.11.2010 09:42
quelle