Wofür ist das flüchtige Schlüsselwort nützlich?


671

Bei der heutigen Arbeit bin ich auf das volatileSchlüsselwort in Java gestoßen. Da ich nicht sehr vertraut damit war, fand ich diese Erklärung:

Java-Theorie und -Praxis: Verwalten der Volatilität

Verwenden Sie es in Anbetracht der Details, in denen dieser Artikel das betreffende Schlüsselwort erläutert, jemals oder könnten Sie jemals einen Fall sehen, in dem Sie dieses Schlüsselwort richtig verwenden könnten?

Antworten:


741

volatilehat Semantik für die Sichtbarkeit des Speichers. Grundsätzlich wird der Wert eines volatileFeldes für alle Leser sichtbar (insbesondere für andere Threads), nachdem ein Schreibvorgang abgeschlossen wurde. Ohne volatilekönnten die Leser einen nicht aktualisierten Wert sehen.

Um Ihre Frage zu beantworten: Ja, ich verwende eine volatileVariable, um zu steuern, ob ein Code eine Schleife fortsetzt. Die Schleife testet den volatileWert und fährt fort, wenn dies der Fall ist true. Die Bedingung kann falsedurch Aufrufen einer "Stop" -Methode festgelegt werden. Die Schleife sieht falseund endet, wenn sie den Wert testet, nachdem die Stoppmethode die Ausführung abgeschlossen hat.

Das Buch " Java Concurrency in Practice ", das ich sehr empfehlen kann, gibt eine gute Erklärung dafür volatile. Dieses Buch wurde von derselben Person verfasst, die den IBM-Artikel geschrieben hat, auf den in der Frage verwiesen wird (tatsächlich zitiert er sein Buch am Ende dieses Artikels). Ich verwende volatiledas, was sein Artikel als "Muster-1-Statusflag" bezeichnet.

Wenn Sie mehr darüber erfahren möchten, wie volatileunter der Haube funktioniert, lesen Sie das Java-Speichermodell . Wenn Sie über dieses Niveau hinausgehen möchten, lesen Sie ein gutes Buch zur Computerarchitektur wie Hennessy & Patterson und lesen Sie mehr über Cache-Kohärenz und Cache-Konsistenz.


118
Diese Antwort ist richtig, aber unvollständig. Eine wichtige Eigenschaft volatile, die mit dem in JSR 133 definierten neuen Java-Speichermodell verbunden ist, wird weggelassen: Wenn ein Thread eine volatileVariable liest , sieht er nicht nur den Wert, der zuletzt von einem anderen Thread in ihn geschrieben wurde, sondern auch alle anderen Schreibvorgänge in andere Variablen, die waren zum Zeitpunkt des volatileSchreibens in diesem anderen Thread sichtbar . Siehe diese Antwort und diese Referenz .
Adam Zalcman

46
Für Anfänger würde ich Sie bitten, mit etwas Code zu demonstrieren (bitte?)
Hungry Blue Dev

6
Der in der Frage verlinkte Artikel enthält Codebeispiele.
Greg Mattes

Ich denke, der Link 'Hennessy & Patterson' ist unterbrochen. Der Link zum Java-Speichermodell führt tatsächlich zur Java-Sprachspezifikation von Oracle, Kapitel 17. Threads und Sperren.
Kris

2
@fefrei: "sofort" ist eine umgangssprachliche Bezeichnung. Dies kann natürlich nicht garantiert werden, wenn weder Ausführungszeitpunkt noch Thread-Planungsalgorithmen tatsächlich angegeben sind. Die einzige Möglichkeit für ein Programm, herauszufinden, ob ein flüchtiger Lesevorgang auf einen bestimmten flüchtigen Schreibvorgang folgt, besteht darin, zu überprüfen, ob der gesehene Wert der erwartete geschriebene Wert ist.
Holger

177

"... der flüchtige Modifikator garantiert, dass jeder Thread, der ein Feld liest, den zuletzt geschriebenen Wert sieht." - Josh Bloch

Wenn Sie über die Verwendung nachdenken volatile, lesen Sie das Paket, java.util.concurrentdas sich mit atomarem Verhalten befasst.

Der Wikipedia-Beitrag zu einem Singleton-Muster zeigt eine flüchtige Verwendung.


18
Warum gibt es beide volatileund synchronizedSchlüsselwörter?
Ptkato

5
Der Wikipedia-Artikel über ein Singleton-Muster hat sich seitdem stark verändert und enthält dieses volatileBeispiel nicht mehr. Es kann in einer archivierten Version gefunden werden .
bskp

1
@ptkato Diese beiden Schlüsselwörter dienen völlig unterschiedlichen Zwecken, daher ist die Frage im Vergleich nicht sehr sinnvoll, obwohl sie beide mit der Parallelität zusammenhängen. Es ist wie zu sagen "Warum gibt es beide voidund publicSchlüsselwörter".
DavidS

134

Wichtiger Punkt zu volatile:

  1. Synchronisation in Java ist möglich durch die Verwendung von Java Schlüsselwort synchronizedund volatileund Schlösser.
  2. In Java können wir keine synchronizedVariablen haben. Die Verwendung eines synchronizedSchlüsselworts mit einer Variablen ist unzulässig und führt zu einem Kompilierungsfehler. Anstatt die synchronizedVariable in Java zu verwenden, können Sie auch die Java- volatileVariable verwenden, die JVM-Threads anweist, den Wert der volatileVariablen aus dem Hauptspeicher zu lesen und nicht lokal zwischenzuspeichern.
  3. Wenn eine Variable nicht von mehreren Threads gemeinsam genutzt wird, muss das volatileSchlüsselwort nicht verwendet werden.

Quelle

Beispiel für die Verwendung von volatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

Wir erstellen die Instanz träge zum Zeitpunkt der ersten Anfrage.

Wenn wir die _instanceVariable nicht volatileerstellen, kann der Thread, der die Instanz von erstellt, Singletonnicht mit dem anderen Thread kommunizieren. Wenn Thread A eine Singleton-Instanz erstellt und die CPU unmittelbar nach der Erstellung beschädigt wird, können alle anderen Threads den Wert von _instancenicht als null anzeigen und glauben, dass ihm immer noch null zugewiesen ist.

Warum passiert das? Da Reader-Threads nicht gesperrt werden und der Writer-Thread erst aus einem synchronisierten Block herauskommt, wird der Speicher nicht synchronisiert und der Wert von _instancewird im Hauptspeicher nicht aktualisiert. Mit dem Schlüsselwort Volatile in Java wird dies von Java selbst behandelt, und solche Aktualisierungen sind für alle Reader-Threads sichtbar.

Schlussfolgerung : Das volatileSchlüsselwort wird auch verwendet, um den Speicherinhalt zwischen Threads zu kommunizieren.

Beispiel Verwendung von ohne flüchtig:

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

Der obige Code ist nicht threadsicher. Obwohl der Wert der Instanz innerhalb des synchronisierten Blocks (aus Leistungsgründen) erneut überprüft wird, kann der JIT-Compiler den Bytecode so neu anordnen, dass der Verweis auf die Instanz festgelegt wird, bevor der Konstruktor seine Ausführung beendet hat. Dies bedeutet, dass die Methode getInstance () ein Objekt zurückgibt, das möglicherweise nicht vollständig initialisiert wurde. Um den Code threadsicher zu machen, kann das Schlüsselwort volatile seit Java 5 für die Instanzvariable verwendet werden. Variablen, die als flüchtig markiert sind, werden für andere Threads erst sichtbar, wenn der Konstruktor des Objekts seine Ausführung vollständig abgeschlossen hat.
Quelle

Geben Sie hier die Bildbeschreibung ein

volatileVerwendung in Java :

Die ausfallsicheren Iteratoren werden normalerweise mithilfe eines volatileZählers für das Listenobjekt implementiert .

  • Wenn die Liste aktualisiert wird, wird der Zähler erhöht.
  • Wenn ein Iteratorerstellt wird, wird der aktuelle Wert des Zählers in das IteratorObjekt eingebettet .
  • Wenn eine IteratorOperation ausgeführt wird, vergleicht die Methode die beiden Zählerwerte und löst a aus, ConcurrentModificationExceptionwenn sie unterschiedlich sind.

Die Implementierung von ausfallsicheren Iteratoren ist normalerweise leicht. Sie stützen sich normalerweise auf Eigenschaften der Datenstrukturen der spezifischen Listenimplementierung. Es gibt kein allgemeines Muster.


2
"Die ausfallsicheren Iteratoren werden normalerweise mit einem flüchtigen Zähler implementiert" - nicht mehr der Fall, zu teuer: bugs.java.com/bugdatabase/view_bug.do?bug_id=6625725
Vsevolod Golovanov

Sind die Doppelprüfungen auf _instance sicher? Ich dachte, sie sind nicht sicher, auch mit flüchtigen
Dexters

"Dadurch werden JVM-Threads angewiesen, den Wert der flüchtigen Variablen aus dem Hauptspeicher zu lesen und nicht lokal zwischenzuspeichern." guter Punkt
Humoyun Ahmad

Zur Gewindesicherheit könnte man auch mitgehen private static final Singleton _instance;.
Chris311

52

volatile ist sehr nützlich, um Threads zu stoppen.

Nicht, dass Sie Ihre eigenen Threads schreiben sollten, Java 1.6 hat viele nette Thread-Pools. Wenn Sie jedoch sicher sind, dass Sie einen Thread benötigen, müssen Sie wissen, wie Sie ihn stoppen können.

Das Muster, das ich für Threads verwende, ist:

public class Foo extends Thread {

  private volatile boolean close = false;

  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

Im obigen Codesegment unterscheidet sich das Lesen des Threads closein der while-Schleife von dem, der aufgerufen wird close(). Ohne flüchtig kann der Thread, der die Schleife ausführt, die Änderung möglicherweise nie schließen.

Beachten Sie, dass keine Synchronisierung erforderlich ist


2
Ich frage mich, warum das überhaupt notwendig ist. Ist das nicht nur notwendig, wenn andere Threads auf die Statusänderung dieses Threads so reagieren müssen, dass die Thread-Synchronisation gefährdet ist?
Jori

27
@Jori, du brauchst flüchtig, weil sich das Schließen des Threads in der while-Schleife von dem unterscheidet, der close () aufruft. Ohne flüchtig kann der Thread, der die Schleife ausführt, die Änderung möglicherweise nie schließen.
Pyrolistical

Würden Sie sagen, dass es einen Vorteil gibt, einen solchen Thread zu stoppen oder die Methoden Thread # Interrupt () und Thread # isInterrupted () zu verwenden?
Ricardo Belchior

2
@Pyrolistical - Haben Sie beobachtet, dass der Thread die Änderung in der Praxis nie gesehen hat? Oder können Sie das Beispiel erweitern, um dieses Problem zuverlässig auszulösen? Ich bin neugierig, weil ich weiß, dass ich Code verwendet habe (und andere gesehen habe), der im Grunde mit dem Beispiel identisch ist, aber ohne das volatileSchlüsselwort, und es scheint immer gut zu funktionieren.
Uhr

2
@aroth: Mit den heutigen JVMs können Sie beobachten, dass Sie dieses Verhalten in der Praxis selbst mit den einfachsten Beispielen nicht zuverlässig reproduzieren können. Bei komplexeren Anwendungen haben Sie manchmal andere Aktionen mit Garantien für die Sichtbarkeit des Speichers in Ihrem Code, die dazu führen, dass er funktioniert. Dies ist besonders gefährlich, da Sie nicht wissen, warum er funktioniert, und eine einfache, anscheinend nicht verwandte Änderung Ihres Codes Ihren Code beschädigen kann Bewerbung…
Holger

31

Ein häufiges Beispiel für die Verwendung volatileist die Verwendung einer volatile booleanVariablen als Flag zum Beenden eines Threads. Wenn Sie einen Thread gestartet haben und ihn sicher von einem anderen Thread aus unterbrechen möchten, können Sie den Thread regelmäßig ein Flag überprüfen lassen. Um es zu stoppen, setzen Sie das Flag auf true. Indem Sie das Flag setzen volatile, können Sie sicherstellen, dass der Thread, der es überprüft, sieht, dass es beim nächsten Überprüfen gesetzt wurde, ohne einen synchronizedBlock verwenden zu müssen.


27

Eine mit volatileSchlüsselwort deklarierte Variable hat zwei Hauptqualitäten, die sie zu etwas Besonderem machen.

  1. Wenn wir eine flüchtige Variable haben, kann sie von keinem Thread im Cache-Speicher des Computers (Mikroprozessor) zwischengespeichert werden. Der Zugriff erfolgte immer aus dem Hauptspeicher.

  2. Wenn eine Schreiboperation für eine flüchtige Variable ausgeführt wird und plötzlich eine Leseoperation angefordert wird, wird garantiert, dass die Schreiboperation vor der Leseoperation beendet wird .

Zwei oben genannte Eigenschaften leiten das ab

  • Alle Threads, die eine flüchtige Variable lesen, lesen definitiv den neuesten Wert. Weil kein zwischengespeicherter Wert ihn verschmutzen kann. Außerdem wird die Leseanforderung erst nach Abschluss des aktuellen Schreibvorgangs gewährt.

Und auf der anderen Seite,

  • Wenn wir die von mir erwähnte Nummer 2 weiter untersuchen , können wir sehen, dass das volatileSchlüsselwort ein idealer Weg ist, um eine gemeinsam genutzte Variable zu verwalten, die 'n' Anzahl von Reader-Threads und nur einen Writer-Thread hat, um darauf zuzugreifen. Sobald wir das volatileSchlüsselwort hinzugefügt haben , ist es fertig. Kein weiterer Aufwand für die Gewindesicherheit.

Umgekehrt,

Wir können das volatileSchlüsselwort nicht nur verwenden, um eine gemeinsam genutzte Variable zu erfüllen, auf die mehr als ein Writer-Thread zugreift .


3
Dies erklärt den Unterschied zwischen flüchtig und synchronisiert.
Ajay

13

Niemand hat die Behandlung der Lese- und Schreiboperation für lange und doppelte variable Typen erwähnt. Lese- und Schreibvorgänge sind atomare Operationen für Referenzvariablen und für die meisten primitiven Variablen, mit Ausnahme von langen und doppelten Variablentypen, bei denen das Schlüsselwort volatile verwendet werden muss, um atomare Operationen zu sein. @Verknüpfung


Um es noch klarer zu machen, muss KEIN Boolescher Wert flüchtig gesetzt werden, da das Lesen und Schreiben eines Booleschen Wertes BEREITS atomar ist.
Kai Wang

2
@KaiWang Sie müssen für Atomizitätszwecke keine flüchtigen Booleschen Werte verwenden. Aber Sie könnten sicherlich aus Sichtbarkeitsgründen. Wolltest du das sagen?
SusanW

12

Ja, flüchtig muss immer dann verwendet werden, wenn mehrere Threads auf eine veränderbare Variable zugreifen möchten. Dies ist nicht sehr häufig der Fall, da Sie normalerweise mehr als eine einzelne atomare Operation ausführen müssen (z. B. den Variablenstatus überprüfen, bevor Sie ihn ändern). In diesem Fall würden Sie stattdessen einen synchronisierten Block verwenden.


10

Meiner Meinung nach sind zwei wichtige Szenarien außer dem Stoppen des Threads, in dem das flüchtige Schlüsselwort verwendet wird, folgende:

  1. Doppelter Verriegelungsmechanismus . Wird häufig in Singleton-Entwurfsmustern verwendet. In diesem Fall muss das Singleton-Objekt als flüchtig deklariert werden .
  2. Falsche Weckrufe . Der Thread wird manchmal vom Warteanruf geweckt, selbst wenn kein Benachrichtigungsanruf ausgegeben wurde. Dieses Verhalten wird als falsches Aufwecken bezeichnet. Dem kann mit einer Bedingungsvariablen (Boolesches Flag) entgegengewirkt werden. Setzen Sie den Aufruf wait () in eine while-Schleife, solange das Flag wahr ist. Wenn der Thread aus einem anderen Grund als Notify / NotifyAll aus dem Warteaufruf erwacht, ist das Flag "Begegnung" weiterhin wahr und die Anrufe warten daher erneut. Setzen Sie dieses Flag vor dem Aufruf von notify auf true. In diesem Fall wird das Boolesche Flag als flüchtig deklariert .

Der gesamte Abschnitt Nr. 2 scheint sehr verwirrt zu sein. Er vereint verlorene Benachrichtigungen, falsche Aufweckvorgänge und Probleme mit der Sichtbarkeit des Speichers. Auch wenn alle Verwendungen des Flags synchronisiert sind, ist flüchtig redundant. Ich denke, ich verstehe, aber falsches Aufwachen ist nicht der richtige Begriff. Bitte klären Sie.
Nathan Hughes

5

Sie müssen das Schlüsselwort 'volatile' oder 'synchronized' und alle anderen Tools und Techniken zur Parallelitätskontrolle verwenden, die Ihnen möglicherweise zur Verfügung stehen, wenn Sie eine Multithread-Anwendung entwickeln. Ein Beispiel für eine solche Anwendung sind Desktop-Apps.

Wenn Sie eine Anwendung entwickeln, die auf dem Anwendungsserver (Tomcat, JBoss AS, Glassfish usw.) bereitgestellt wird, müssen Sie die Parallelitätskontrolle nicht selbst durchführen, da sie bereits vom Anwendungsserver adressiert wird. Wenn ich mich richtig erinnere, verbietet der Java EE-Standard jegliche Parallelitätskontrolle in Servlets und EJBs, da er Teil der Infrastrukturschicht ist, von der Sie angenommen haben, dass sie nicht mehr damit umgehen kann. Sie führen in einer solchen App nur eine Parallelitätskontrolle durch, wenn Sie Singleton-Objekte implementieren. Dies wurde sogar bereits behoben, wenn Sie Ihre Komponenten mit Frameworkd wie Spring stricken.

In den meisten Fällen der Java-Entwicklung, in denen die Anwendung eine Webanwendung ist und ein IoC-Framework wie Spring oder EJB verwendet, müssen Sie "volatile" nicht verwenden.


5

volatilegarantiert nur, dass alle Threads, auch sie selbst, inkrementiert werden. Beispiel: Ein Zähler sieht zur gleichen Zeit dasselbe Gesicht der Variablen. Es wird nicht anstelle von synchronisiertem oder atomarem oder anderem Material verwendet, sondern macht die Lesevorgänge vollständig synchronisiert. Bitte vergleichen Sie es nicht mit anderen Java-Schlüsselwörtern. Wie das folgende Beispiel zeigt, sind flüchtige variable Operationen ebenfalls atomar, sie schlagen fehl oder sind sofort erfolgreich.

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

Selbst wenn Sie volatile oder nicht volatile Ergebnisse erzielen, werden diese immer unterschiedlich ausfallen. Wenn Sie jedoch AtomicInteger wie folgt verwenden, sind die Ergebnisse immer gleich. Dies gilt auch für synchronisierte.

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

4

Ja, ich benutze es ziemlich oft - es kann sehr nützlich für Multithread-Code sein. Der Artikel, auf den Sie hingewiesen haben, ist gut. Es gibt jedoch zwei wichtige Dinge zu beachten:

  1. Sie sollten volatile nur verwenden, wenn Sie vollständig verstehen, was es tut und wie es sich von synchronisiert unterscheidet. In vielen Situationen scheint flüchtig eine einfachere und leistungsfähigere Alternative zu synchronisiert zu sein, wenn ein besseres Verständnis von flüchtig häufig klar macht, dass synchronisiert die einzige Option ist, die funktionieren würde.
  2. volatile funktioniert in vielen älteren JVMs nicht, obwohl synchronisiert dies tut. Ich erinnere mich, dass ich ein Dokument gesehen habe, in dem auf die verschiedenen Unterstützungsstufen in verschiedenen JVMs verwiesen wurde, aber leider kann ich es jetzt nicht finden. Überprüfen Sie dies auf jeden Fall, wenn Sie Java vor 1.5 verwenden oder wenn Sie keine Kontrolle über die JVMs haben, auf denen Ihr Programm ausgeführt wird.

4

Jeder Thread, der auf ein flüchtiges Feld zugreift, liest seinen aktuellen Wert, bevor er fortfährt, anstatt (möglicherweise) einen zwischengespeicherten Wert zu verwenden.

Nur die Mitgliedsvariable kann flüchtig oder vorübergehend sein.


3

Absolut ja. (Und das nicht nur in Java, sondern auch in C #.) Es gibt Zeiten, in denen Sie einen Wert abrufen oder festlegen müssen, der garantiert eine atomare Operation auf Ihrer bestimmten Plattform ist, z. B. ein int oder ein boolescher Wert, aber nicht erforderlich der Overhead der Thread-Verriegelung. Mit dem Schlüsselwort volatile können Sie sicherstellen, dass Sie beim Lesen des Werts den aktuellen Wert erhalten und keinen zwischengespeicherten Wert, der gerade durch ein Schreiben in einen anderen Thread veraltet wurde.


3

Es gibt zwei verschiedene Verwendungen von flüchtigen Schlüsselwörtern.

  1. Verhindert, dass JVM Werte aus dem Register liest (als Cache angenommen), und erzwingt das Lesen seines Werts aus dem Speicher.
  2. Reduziert das Risiko von Speicherinkonsistenzfehlern.

Verhindert, dass JVM Werte im Register liest, und erzwingt das Lesen seines Werts aus dem Speicher.

Ein Besetzt-Flag wird verwendet, um zu verhindern, dass ein Thread fortgesetzt wird, während das Gerät beschäftigt ist und das Flag nicht durch eine Sperre geschützt ist:

while (busy) {
    /* do something else */
}

Der Test-Thread wird fortgesetzt, wenn ein anderer Thread das Besetzt-Flag ausschaltet :

busy = 0;

Da jedoch im Testthread häufig auf Besetzt zugegriffen wird, kann die JVM den Test optimieren, indem sie den Wert von Besetzt in ein Register legt und dann den Inhalt des Registers testet, ohne den Wert von Besetzt im Speicher vor jedem Test zu lesen. Der Test-Thread würde niemals eine Besetzt-Änderung sehen und der andere Thread würde nur den Wert von Besetzt im Speicher ändern, was zu einem Deadlock führen würde. Wenn Sie das Besetzt-Flag als flüchtig deklarieren, wird sein Wert vor jedem Test gelesen.

Reduziert das Risiko von Speicherkonsistenzfehlern.

Die Verwendung flüchtiger Variablen verringert das Risiko von Speicherkonsistenzfehlern , da beim Schreiben in eine flüchtige Variable eine "Vorher-passiert" -Beziehung mit nachfolgenden Lesevorgängen derselben Variablen hergestellt wird. Dies bedeutet, dass Änderungen an einer flüchtigen Variablen für andere Threads immer sichtbar sind.

Die Technik des Lesens und Schreibens ohne Speicherkonsistenzfehler wird als atomare Aktion bezeichnet .

Eine atomare Aktion ist eine, die effektiv auf einmal stattfindet. Eine atomare Aktion kann nicht in der Mitte aufhören: Sie geschieht entweder vollständig oder überhaupt nicht. Bis zum Abschluss der Aktion sind keine Nebenwirkungen einer atomaren Aktion sichtbar.

Im Folgenden finden Sie Aktionen, die Sie angeben können und die atomar sind:

  • Lese- und Schreibvorgänge sind für Referenzvariablen und für die meisten primitiven Variablen (alle Typen außer long und double) atomar.
  • Lese- und Schreibvorgänge sind für alle als flüchtig deklarierten Variablen (einschließlich langer und doppelter Variablen) atomar .

Prost!


2

Volatile macht folgendes.

1> Das Lesen und Schreiben flüchtiger Variablen durch verschiedene Threads erfolgt immer aus dem Speicher, nicht aus dem eigenen Cache oder CPU-Register des Threads. Jeder Thread befasst sich also immer mit dem neuesten Wert. 2> Wenn zwei verschiedene Threads mit derselben Instanz oder statischen Variablen im Heap arbeiten, werden die Aktionen anderer möglicherweise als nicht in Ordnung angesehen. Siehe dazu Jeremy Mansons Blog. Aber flüchtig hilft hier.

Der folgende vollständig ausgeführte Code zeigt, wie eine Reihe von Threads in vordefinierter Reihenfolge ausgeführt und Ausgaben ohne Verwendung eines synchronisierten Schlüsselworts gedruckt werden können.

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

Um dies zu erreichen, können wir den folgenden vollwertigen laufenden Code verwenden.

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

Der folgende Github-Link enthält eine Readme-Datei, die eine angemessene Erklärung enthält. https://github.com/sankar4git/volatile_thread_ordering


2

volatilesagt für einen Programmierer, dass der Wert immer aktuell sein wird. Das Problem ist, dass der Wert auf verschiedenen Arten von Hardwarespeicher gespeichert werden kann. Zum Beispiel können es CPU-Register, CPU-Cache, RAM ... sein. UPU-Register und CPU-Cache gehören zur CPU und können im Gegensatz zu RAM, das in der Multithreading-Umgebung auf der Rettung ist, keine Daten gemeinsam nutzen

Geben Sie hier die Bildbeschreibung ein

volatileDas Schlüsselwort besagt, dass eine Variable direkt aus dem / in den RAM-Speicher gelesen und geschrieben wird . Es hat einen gewissen Rechenaufwand

Java 5erweitert volatiledurch die Unterstützung von happens-before[About]

Ein Schreibvorgang in ein flüchtiges Feld erfolgt vor jedem nachfolgenden Lesen dieses Feldes.

volatileStichwort nicht heilen eine race conditionSituation , wenn mehrere Threads können schreiben gleichzeitig einige Werte. Die Antwort lautet synchronizedSchlüsselwort [Info]

Infolgedessen ist es nur dann sicher , wenn ein Thread schreibt und andere nur den volatileWert lesen

flüchtig vs synchronisiert


1

Von der Oracle - Dokumentation Seite ergibt sich die Notwendigkeit für flüchtigen Variable fix Speicherkonsistenzprobleme:

Die Verwendung flüchtiger Variablen verringert das Risiko von Speicherkonsistenzfehlern, da durch das Schreiben in eine flüchtige Variable eine Beziehung hergestellt wird, bevor sie mit nachfolgenden Lesevorgängen derselben Variablen ausgeführt werden.

Dies bedeutet, dass Änderungen an einer volatileVariablen für andere Threads immer sichtbar sind. Dies bedeutet auch, dass ein Thread beim Lesen einer flüchtigen Variablen nicht nur die letzte Änderung an der volatile, sondern auch die Nebenwirkungen des Codes sieht , der die Änderung ausgelöst hat.

Wie in der Peter ParkerAntwort erläutert , volatilekann der Stapel jedes Threads ohne Modifikator eine eigene Kopie der Variablen haben. Durch das Festlegen der Variablen als volatilewurden Speicherkonsistenzprobleme behoben.

Schauen Sie sich zum besseren Verständnis die jenkov- Tutorial-Seite an.

In der zugehörigen SE-Frage finden Sie weitere Informationen zu flüchtigen und Anwendungsfällen für die Verwendung von flüchtigen Stoffen:

Unterschied zwischen flüchtig und synchronisiert in Java

Ein praktischer Anwendungsfall:

Sie haben viele Threads, die die aktuelle Zeit in einem bestimmten Format drucken müssen, zum Beispiel : java.text.SimpleDateFormat("HH-mm-ss"). Sie können eine Klasse haben, die die aktuelle Zeit in eine SimpleDateFormatVariable umwandelt und diese für jede Sekunde aktualisiert. Alle anderen Threads können diese flüchtige Variable einfach verwenden, um die aktuelle Zeit in Protokolldateien zu drucken.


1

Flüchtige Variablen sind leichte Synchronisation. Wenn die Sichtbarkeit der neuesten Daten für alle Threads erforderlich ist und die Atomizität beeinträchtigt werden kann, müssen in solchen Situationen flüchtige Variablen bevorzugt werden. Beim Lesen von flüchtigen Variablen wird immer der letzte Schreibvorgang zurückgegeben, der von einem Thread ausgeführt wurde, da sie weder in Registern noch in Caches zwischengespeichert werden, die andere Prozessoren nicht sehen können. Flüchtig ist sperrenfrei. Ich benutze volatile, wenn das Szenario die oben genannten Kriterien erfüllt.


-1

Der flüchtige Schlüssel stellt bei Verwendung mit einer Variablen sicher, dass Threads, die diese Variable lesen, denselben Wert sehen. Wenn Sie nun mehrere Threads haben, die in eine Variable lesen und schreiben, reicht es nicht aus, die Variable flüchtig zu machen, und die Daten werden beschädigt. Bild-Threads haben den gleichen Wert gelesen, aber jeder hat einige Änderungen vorgenommen (z. B. einen Zähler erhöht). Beim Zurückschreiben in den Speicher wird die Datenintegrität verletzt. Aus diesem Grund muss die Variable synchronisiert werden (verschiedene Möglichkeiten sind möglich).

Wenn die Änderungen von einem Thread vorgenommen werden und die anderen nur diesen Wert lesen müssen, ist der flüchtige Stoff geeignet.


-1

Die flüchtige Variable wird grundsätzlich für die sofortige Aktualisierung (Flush) in der gemeinsam genutzten Hauptcachezeile nach der Aktualisierung verwendet, sodass Änderungen sofort auf alle Arbeitsthreads übertragen werden.


-2

Im Folgenden finden Sie einen sehr einfachen Code, der die Anforderung einer volatileVariablen demonstriert, mit der die Thread-Ausführung von einem anderen Thread aus gesteuert wird (dies ist ein Szenario, in dem dies volatileerforderlich ist).

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

Wenn volatilenicht verwendet: Die Meldung " Gestoppt am: xxx " wird auch nach " Anhalten am: xxx " nie angezeigt , und das Programm wird weiterhin ausgeführt.

Stopping on: 1895303906650500

Bei volatileVerwendung: Sie sehen sofort " Gestoppt am: xxx ".

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

Demo: https://repl.it/repls/SilverAgonizingObjectcode


Zum Downvoter: Möchtest du erklären, warum Downvote? Wenn dies nicht stimmt, werde ich zumindest lernen, was falsch ist. Ich habe den gleichen Kommentar zweimal hinzugefügt, weiß aber nicht, wer immer wieder
löscht

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.