Die flüchtige Variable in der Java -Sprache kann als "leichtere synchronisierte" angesehen werden. Im Vergleich zum synchronisierten Block erfordert die volatile Variable weniger Codierung und Laufzeitaufwand, aber die Funktionalität, die sie erreichen kann, ist nur ein Teil der synchronisierten.
Sperren
Sperren bieten zwei Hauptmerkmale: gegenseitige Ausschluss und Sichtbarkeit.
Volatile Variablen
Die volatile Variable hat die Sichtbarkeitseigenschaften von synchronisiert, hat jedoch nicht die atomaren Eigenschaften. Dies bedeutet, dass der Thread automatisch den neuesten Wert der volatilen Variablen ermitteln kann.
Volatile Variablen können verwendet werden, um die Gewindesicherheit zu gewährleisten, aber nur auf einen sehr begrenzten Satz von Anwendungsfällen angewendet werden: Es besteht keine Einschränkung zwischen mehreren Variablen oder zwischen dem aktuellen Wert einer Variablen und dem geänderten Wert. Daher reicht die Verwendung von volatilen allein nicht aus, um Zähler, Mutexes oder eine Klasse mit Invarianten zu implementieren, die mit mehreren Variablen zugeordnet sind (z. B. "Start <= Ende").
Einfachheit oder Skalierbarkeit können Sie in der Regel flüchtige Variablen anstelle von Schlösser verwenden. Einige Redewendungen sind leichter zu codieren und zu lesen, wenn sie flüchtige Variablen anstelle von Sperren verwenden. Darüber hinaus verursacht die volatile Variable keine Gewindeblockade wie ein Schloss und verursacht daher selten Skalierbarkeitsprobleme. In einigen Fällen kann die volatile Variable auch Leistungsvorteile gegenüber Sperren bieten, wenn der Lesevorgang viel größer ist als der Schreibvorgang.
Bedingungen für die korrekte Verwendung von volatilen Variablen
Sie können die volatile Variable nur in begrenzten Fällen anstelle von Sperren verwenden. Um die variable ideale Gewindesicherheit zu ermöglichen, müssen die folgenden zwei Bedingungen gleichzeitig erfüllt werden:
Schreibvorgänge in Variablen hängen nicht vom aktuellen Wert ab.
Diese Variable ist nicht im Invarianten mit anderen Variablen enthalten.
Tatsächlich zeigen diese Bedingungen, dass diese gültigen Werte, die in die variable Variable geschrieben werden können, unabhängig vom Status eines beliebigen Programms sind, einschließlich des aktuellen Zustands der Variablen.
Die ersten Bedingungsgrenzen verhindern, dass die flüchtige Variable als Gewindefach verwendet wird. Obwohl ein inkrementeller Betrieb (X ++) wie ein separater Vorgang aussieht, ist es tatsächlich eine kombinierte Operation, die aus einer Abfolge von read-modify-write-Operationen besteht, die atomisch durchgeführt werden müssen, und flüchtig kann nicht die erforderlichen Atomeigenschaften bereitgestellt werden. Um den korrekten Betrieb zu implementieren, muss der Wert von X während des Betriebs konstant bleiben, was mit der volatilen Variablen nicht möglich ist. (Wenn der Wert jedoch nur aus einem einzigen Thread geschrieben wird, kann die erste Bedingung ignoriert werden.)
Die meisten Programmiersituationen widersprechen mit einer dieser beiden Bedingungen, sodass die volatile Variable nicht so allgemein auf die Sicherheit der Gewinde wie synchron anwendbar ist. Listing 1 zeigt eine nicht-thread-safe numerische Reichweite. Es enthält eine Invariante - die untere Grenze ist immer geringer als oder gleich der Obergrenze.
Geben Sie ein Beispiel an
Sehen wir unten ein Beispiel. Wir implementieren einen Zähler. Jedes Mal, wenn der Thread startet, wird die Zähler -Inc -Methode aufgerufen, um den Zähler zur Ausführungsumgebung hinzuzufügen - JDK -Version: JDK1.6.0_31, Speicher: 3G CPU: x86 2.4g
öffentliche Klasse Zähler {public static int count = 0; public static void inc () {// Die Verzögerung hier ist 1 Millisekunde, wodurch das Ergebnis offensichtlich versucht wird {Thread.sleep (1); } catch (InterruptedException e) {} count ++; } public static void main (String [] args) {// 1000 Threads gleichzeitig starten, um i ++ -Rechnungen durchzuführen und das tatsächliche Ergebnis für (int i = 0; i <1000; i ++) {neuer Thread (new Runnable () {@Override public void run () {counter ();}}). } // Der Wert jedes Laufs hier kann unterschiedlich sein, möglicherweise 1000 System.out.println ("Run Ergebnis: count.count =" + counter.count); }} Auslaufergebnis: count.count = 995
Das tatsächliche Betriebsergebnis kann jedes Mal unterschiedlich sein. Das Ergebnis der Maschine ist: Auslaufergebnis: Zähler.Count = 995. Es ist zu erkennen, dass die Anzahl in einer Umgebung mit mehreren Threads nicht erwartet, dass das Ergebnis 1000 beträgt.
Viele Menschen denken, dass dies ein Problem mit mehreren Thread-Parallelen ist. Sie müssen nur vor der variablen Anzahl volatile hinzufügen, um dieses Problem zu vermeiden. Dann ändern wir den Code, um festzustellen, ob das Ergebnis unseren Erwartungen entspricht.
Public Class Counter {public volatile statische int count = 0; public static void inc () {// Die Verzögerung hier ist 1 Millisekunde, wodurch das Ergebnis offensichtlich versucht wird {Thread.sleep (1); } catch (InterruptedException e) {} count ++; } public static void main (string [] args) {// 1000 Threads gleichzeitig starten, i ++ berechnungen durchführen und das tatsächliche Ergebnis für (int i = 0; i <1000; i ++) {neuer Thread (neuer Runnable () {@Override public void run () {coup.inc ();}}). } // Der Wert eines jeden Laufs hier kann unterschiedlich sein, möglicherweise 1000 system.out.println ("Run Ergebnis: count.count =" + counter.count); }}Auslaufergebnis: count.count = 992
Das Betriebsergebnis ist immer noch nicht so 1000, wie wir erwartet hatten. Lassen Sie uns die folgenden Gründe analysieren
Im Artikel der Java -Müllsammlung wird die Zuweisung des Gedächtnisses im Moment von JVM beschrieben. Einer der Speicherbereiche ist der JVM Virtual Machine Stack. Jeder Thread verfügt über einen Thread -Stapel, wenn er ausgeführt wird, und der Thread -Stapel speichert die Informationen über den variablen Wert während der Thread -Ausführungen. Wenn ein Thread auf den Wert eines bestimmten Objekts zugreift, findet er zuerst den Wert der Variablen, der dem Heap -Speicher über die Referenz des Objekts entspricht, und lädt dann den spezifischen Wert der Heap -Speichervariablen in den lokalen Speicher des Threads, um eine Variablenkopie zu erstellen. Danach hat der Thread keine Beziehung mehr zum HEAP -Speicher -Variablenwert des Objekts, sondern verändert direkt den Wert der Kopiervariablen und schreibt zu einem bestimmten Zeitpunkt nach der Änderung (vor dem Thread) automatisch den Wert der Thread -Variablen zurück in die HEAP -Variable des Objekts. Auf diese Weise ändert sich der Wert des Objekts im Haufen. Das folgende Bild beschreibt die Interaktion dieses Schreibens
EAD- und Laden Sie Kopiervariablen vom Hauptspeicher zum aktuellen Arbeitsspeicher
Verwenden und zuweisen ausführende Code, um den freigegebenen Variablenwert zu ändern
Speichern und schreiben
Wo verwendet und zuweisen können, können mehrmals angezeigt werden
Diese Operationen sind jedoch nicht atomar, dh nach der Änderung der Hauptspeicherzahlvariablen führt der Wert im Arbeitsspeicher des Threads nicht zu entsprechenden Änderungen, da sie geladen wurde. Das berechnete Ergebnis unterscheidet sich daher von den Erwartungen.
Für Variablen, die durch volatile modifiziert wurden, stellt die virtuelle JVM -Maschine nur sicher, dass der von dem Hauptspeicher zum Thread -Arbeitsspeicher geladene Wert der neueste ist
Wenn beispielsweise Thread 1 und Thread 2 Lese- und Ladevorgänge ausführen und feststellen, dass der Wert der Anzahl im Hauptspeicher 5 ist, wird der neueste Wert geladen
Nachdem die Heap -Anzahl in Thread 1 modifiziert wurde, wird sie in den Hauptspeicher geschrieben, und die Zählvariable im Hauptspeicher wird 6.
Da Thread 2 bereits den Lese- und Ladevorgang durchgeführt hat, wird der variable Wert der Hauptspeicherzahl auch nach dem Vorgang auf 6 aktualisiert.
Dies führt dazu, dass die Parallelität nach zwei Threads mit dem flüchtigen Schlüsselwort rechtzeitig geändert wurde.