web-dev-qa-db-ger.com

Das einfachste und verständlichste Beispiel für ein flüchtiges Schlüsselwort in Java

Ich lese über volatile Keywords in Java und verstehe den theoretischen Teil davon vollständig.

Aber was ich suche, ist ein gutes Fallbeispiel, das zeigt, was passieren würde, wenn Variable nicht flüchtig wäre und wenn es wäre.

Das folgende Code-Snippet funktioniert nicht wie erwartet ( von aioobe )

class Test extends Thread {

    boolean keepRunning = true;

    public void run() {
        while (keepRunning) {
        }

        System.out.println("Thread terminated.");
    }

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println("keepRunning set to false.");
    }
}

Wenn keepRunning nicht flüchtig ist, sollte Thread im Idealfall auf unbestimmte Zeit weiterlaufen. Nach einigen Sekunden hört es jedoch auf.

Ich habe zwei grundlegende Fragen: -

  • Kann jemand flüchtig mit Beispiel erklären? Nicht mit der Theorie von JLS.
  • Ist flüchtiger Ersatz für die Synchronisation? Erreicht es Atomizität?
58
tmgr

Flüchtig -> Garantiert Sichtbarkeit und NICHT Atomizität

Synchronisation (Sperren) -> Garantiert Sichtbarkeit und Atomizität (wenn dies ordnungsgemäß durchgeführt wird)

Volatile ist kein Ersatz für die Synchronisation

Verwenden Sie Volatile nur, wenn Sie die Referenz aktualisieren und keine anderen Vorgänge daran ausführen.

Beispiel:

volatile int i = 0;

public void incrementI(){
   i++;
}

ist ohne Verwendung von Synchronisation oder AtomicInteger nicht threadsicher, da das Inkrementieren eine zusammengesetzte Operation ist.

Warum läuft das Programm nicht unbegrenzt?

Nun, das hängt von verschiedenen Umständen ab. In den meisten Fällen ist JVM intelligent genug, um den Inhalt zu leeren.

Korrekter Einsatz von Volatilem diskutiert verschiedene Einsatzmöglichkeiten von Volatilem. Die Verwendung von Volatile richtig ist schwierig, ich würde sagen "Wenn Sie Zweifel haben, lassen Sie es aus", verwenden Sie stattdessen einen synchronisierten Block.

Ebenfalls:

synchronized Block kann anstelle von volatile verwendet werden, aber die Umkehrung ist nicht wahr

45
Narendra Pathai

Für Ihr spezielles Beispiel: Wenn die Server-JVM nicht als flüchtig deklariert wird, kann sie die Variable keepRunning aus der Schleife entfernen, da sie in der Schleife nicht geändert wird (in eine Endlosschleife), die Client-JVM jedoch nicht. Deshalb sehen Sie unterschiedliche Ergebnisse.

Allgemeine Erklärung zu flüchtigen Variablen folgt:

Wenn ein Feld als volatile deklariert wird, werden der Compiler und die Laufzeit darauf hingewiesen, dass diese Variable gemeinsam genutzt wird und dass Operationen auf ihr nicht mit anderen Speicheroperationen neu angeordnet werden sollten. Flüchtige Variablen werden nicht in Registern oder in Caches zwischengespeichert, in denen sie vor anderen Prozessoren verborgen sind. Ein Lesevorgang einer flüchtigen Variablen gibt immer den letzten Schreibvorgang eines Threads zurück .

Die Sichtbarkeitseffekte flüchtiger Variablen gehen über den Wert der flüchtigen Variablen selbst hinaus. Wenn Thread A in eine flüchtige Variable schreibt und anschließend Thread B dieselbe Variable liest, werden die Werte aller Variablen, die vor dem Schreiben in die flüchtige Variable für A sichtbar waren, nach dem Lesen der flüchtigen Variablen für B sichtbar.

Flüchtige Variablen werden am häufigsten als Abschluss-, Unterbrechungs- oder Statusflag verwendet:

  volatile boolean flag;
  while (!flag)  {
     // do something untill flag is true
  }

Flüchtige Variablen können für andere Arten von Statusinformationen verwendet werden. Bei diesem Versuch ist jedoch mehr Sorgfalt erforderlich. Beispielsweise ist die Semantik von volatile nicht stark genug, um die Inkrementierungsoperation (count++) atomar zu machen, es sei denn, Sie können garantieren, dass die Variable nur aus einem einzelnen Thread geschrieben wird.

Das Verriegeln kann sowohl Sichtbarkeit als auch Atomizität gewährleisten. Flüchtige Variablen können nur die Sichtbarkeit garantieren.

Sie können flüchtige Variablen nur verwenden, wenn alle folgenden Kriterien erfüllt sind:

  • Schreibvorgänge in die Variable hängen nicht von ihrem aktuellen Wert ab, oder Sie können sicherstellen, dass nur ein einziger Thread den Wert aktualisiert.
  • Die Variable ist nicht an Invarianten mit anderen Statusvariablen beteiligt. und
  • Das Sperren ist aus keinem anderen Grund erforderlich, während auf die Variable zugegriffen wird.

Debugging-Tipp : Geben Sie beim Aufrufen der JVM immer den JVM-Befehlszeilenschalter -server an, auch für Entwicklungs- und Testzwecke. Die Server-JVM führt mehr Optimierungen als die Client-JVM durch, z. B. das Entfernen von Variablen aus einer Schleife, die in der Schleife nicht geändert werden. Code, der möglicherweise in der Entwicklungsumgebung (Client-JVM) funktioniert, kann in der Implementierungsumgebung (Server-JVM) fehlerhaft sein.

Dies ist ein Auszug aus "Java Concurrency in Practice" , dem besten Buch, das Sie zu diesem Thema finden können.

27
Sokolov

Ich habe dein Beispiel etwas geändert. Verwenden Sie nun das Beispiel mit keepRunning als flüchtiges und nichtflüchtiges Mitglied:

class TestVolatile extends Thread{
    //volatile
    boolean keepRunning = true;

    public void run() {
        long count=0;
        while (keepRunning) {
            count++;
        }

        System.out.println("Thread terminated." + count);
    }

    public static void main(String[] args) throws InterruptedException {
        TestVolatile t = new TestVolatile();
        t.start();
        Thread.sleep(1000);
        System.out.println("after sleeping in main");
        t.keepRunning = false;
        t.join();
        System.out.println("keepRunning set to " + t.keepRunning);
    }
}
13
paritosht

Was ist ein volatiles Keyword?

volatile Schlüsselwort verhindert caching of variables.

Betrachten Sie den Code zuerst ohne flüchtiges - Schlüsselwort

class MyThread extends Thread {
    private boolean running = true;   //non-volatile keyword

    public void run() {
        while (running) {
            System.out.println("hello");
        }
    }

    public void shutdown() {
        running = false;
    }
}

public class Main {

    public static void main(String[] args) {
        MyThread obj = new MyThread();
        obj.start();

        Scanner input = new Scanner(System.in);
        input.nextLine(); 
        obj.shutdown();   
    }    
}

Idealerweise sollte dieses Programm print hello bis RETURN key gedrückt werden. Bei some machines kann es jedoch vorkommen, dass die Variable running cached ist, und Sie können ihren Wert nicht über die shutdown () -Methode ändern, was dazu führt, dass infinite den Hallo-Text druckt.

Bei Verwendung eines flüchtigen Schlüsselworts ist es also guaranteed, dass Ihre Variable nicht zwischengespeichert wird, dh run fine für all machines

private volatile boolean running = true;  //volatile keyword

Daher ist die Verwendung eines flüchtigen Schlüsselworts ein good und safer programming practice.

9
Prateek Joshi

Variable Volatile : Das volatile Keyword ist auf Variablen anwendbar. Das volatile Schlüsselwort in Java garantiert, dass der Wert der volatile Variable immer aus dem Hauptspeicher und nicht aus dem lokalen Cache von Thread gelesen wird.

Access_Modifier volatile DataType Variable_Name;

Flüchtiges Feld: Eine Angabe an VM, dass mehrere Threads gleichzeitig versuchen, auf den Feldwert zuzugreifen bzw. ihn zu aktualisieren. Zu einer speziellen Art von Instanzvariablen, die von allen Threads mit dem Wert "Geändert" geteilt werden muss. Ähnlich wie bei einer statischen (Klassen-) Variablen wird nur eine Kopie eines flüchtigen Werts im Hauptspeicher zwischengespeichert. Bevor ALU-Vorgänge ausgeführt werden, muss jeder Thread den aktualisierten Wert nach der ALU-Operation aus dem Hauptspeicher lesen und in den Hauptspeicher schreiben. (Ein Schreibvorgang in eine flüchtige Variable v wird synchronisiert - mit allen nachfolgenden Lesevorgängen von v durch einen beliebigen Thread.) Dies bedeutet, dass Änderungen an einer flüchtigen Variablen für andere Threads immer sichtbar sind.

 enter image description here

Hier zu einem nonvoltaile variable Wenn Thread t1 den Wert im Cache von t1 ändert, kann Thread t2 nicht auf den geänderten Wert zugreifen, bis t1 schreibt, t2 wird aus dem Hauptspeicher für den zuletzt geänderten Wert gelesen, was zu Data-Inconsistancy führen kann.

volatile kann nicht zwischengespeichert werden - assembler

    +--------------+--------+-------------------------------------+
    |  Flag Name   |  Value | Interpretation                      |
    +--------------+--------+-------------------------------------+
    | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.|
    +--------------+--------+-------------------------------------+
    |ACC_TRANSIENT | 0x0080 | Declared transient; not written or  |
    |              |        | read by a persistent object manager.|
    +--------------+--------+-------------------------------------+

Shared Variables : Speicher, der von Threads gemeinsam genutzt werden kann, wird als Shared Memory oder Heap Memory bezeichnet. Alle Instanzfelder, statischen Felder und Arrayelemente werden im Heap-Speicher gespeichert.

Synchronisation : synchronized gilt für Methoden, Blöcke. Erlaubt es, jeweils nur 1 Thread für ein Objekt auszuführen. Wenn t1 die Kontrolle übernimmt, müssen die verbleibenden Threads warten, bis sie die Kontrolle freigeben.

Beispiel:

public class VolatileTest implements Runnable {

    private static final int MegaBytes = 10241024;

    private static final Object counterLock = new Object();
    private static int counter = 0;
    private static volatile int counter1 = 0;

    private volatile int counter2 = 0;
    private int counter3 = 0;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            concurrentMethodWrong();
        }

    }

    void addInstanceVolatile() {
        synchronized (counterLock) {
            counter2 = counter2 + 1;
            System.out.println( Thread.currentThread().getName() +"\t\t « InstanceVolatile :: "+ counter2);
        }
    }

    public void concurrentMethodWrong() {
        counter = counter + 1;
        System.out.println( Thread.currentThread().getName() +" « Static :: "+ counter);
        sleepThread( 1/4 );

        counter1 = counter1 + 1;
        System.out.println( Thread.currentThread().getName() +"\t « StaticVolatile :: "+ counter1);
        sleepThread( 1/4 );

        addInstanceVolatile();
        sleepThread( 1/4 );

        counter3 = counter3 + 1;
        sleepThread( 1/4 );
        System.out.println( Thread.currentThread().getName() +"\t\t\t\t\t « Instance :: "+ counter3);
    }
    public static void main(String[] args) throws InterruptedException {
        Runtime runtime = Runtime.getRuntime();

        int availableProcessors = runtime.availableProcessors();
        System.out.println("availableProcessors :: "+availableProcessors);
        System.out.println("MAX JVM will attempt to use : "+ runtime.maxMemory() / MegaBytes );
        System.out.println("JVM totalMemory also equals to initial heap size of JVM : "+ runtime.totalMemory() / MegaBytes );
        System.out.println("Returns the amount of free memory in the JVM : "+ untime.freeMemory() / MegaBytes );
        System.out.println(" ===== ----- ===== ");

        VolatileTest volatileTest = new VolatileTest();
        Thread t1 = new Thread( volatileTest );
        t1.start();

        Thread t2 = new Thread( volatileTest );
        t2.start();

        Thread t3 = new Thread( volatileTest );
        t3.start();

        Thread t4 = new Thread( volatileTest );
        t4.start();

        Thread.sleep( 10 );;

        Thread optimizeation = new Thread() {
            @Override public void run() {
                System.out.println("Thread Start.");

                Integer appendingVal = volatileTest.counter2 + volatileTest.counter2 + volatileTest.counter2;

                System.out.println("End of Thread." + appendingVal);
            }
        };
        optimizeation.start();
    }

    public void sleepThread( long sec ) {
        try {
            Thread.sleep( sec * 1000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Statisch [Class Field] vs Volatile [Instance Field] - Beide werden nicht von Threads zwischengespeichert

  • Statische Felder sind für alle Threads gleich und werden im Methodenbereich gespeichert. Statisch ohne flüchtige Verwendung. Statisches Feld kann nicht serialisiert werden.

  • Volatil wird hauptsächlich mit Instanzvariablen verwendet, die im Heap-Bereich gespeichert werden. Volatile wird hauptsächlich verwendet, um den aktualisierten Wert aller Threads zu erhalten. Instanz flüchtiges Feld kann Serialisiert sein.

@sehen

4
Yash

Wenn eine Variable volatile ist, garantiert sie, dass sie nicht zwischengespeichert wird und unterschiedliche Threads den aktualisierten Wert sehen. Wenn Sie es jedoch nicht als volatile markieren, ist dies nicht für die Opposition garantiert. volatile war eines der Dinge, die in der JVM lange Zeit kaputt waren und immer noch nicht immer gut verstanden wurden.

3
Jeff Storey

Wenn keepRunning nicht flüchtig war, sollte Thread im Idealfall unbegrenzt weiterlaufen. Nach einigen Sekunden hört es jedoch auf.

Wenn Sie auf einem Einzelprozessor laufen oder Ihr System sehr ausgelastet ist, werden die Threads möglicherweise vom Betriebssystem ausgelagert, was zu einer gewissen Cache-Ungültigkeitsstufe führt. Wie bereits erwähnt, bedeutet das Fehlen einer volatile nicht, dass der Speicher nicht gemeinsam genutzt wird. Die JVM versucht jedoch, den Speicher nicht zu synchronisieren, wenn dies aus Leistungsgründen der Fall ist, sodass der Speicher möglicherweise nicht aktualisiert wird.

Beachten Sie außerdem, dass System.out.println(...) synchronisiert wird, da die zugrunde liegende PrintStream die Synchronisation durchführt, um die überlappende Ausgabe zu stoppen. Sie erhalten also die Hauptsynchronisierung der Speichersynchronisation "kostenlos". Dies erklärt jedoch noch nicht, warum die Leseschleife die Aktualisierungen jedoch überhaupt sieht.

Egal ob die println(...)-Zeilen ein- oder ausgehen, Ihr Programm dreht sich für mich unter Java6 auf einem MacBook Pro mit Intel i7.

Kann jemand flüchtig mit Beispiel erklären? Nicht mit der Theorie von JLS.

Ich denke, dein Beispiel ist gut. Nicht sicher, warum es nicht funktioniert, wenn alle System.out.println(...)-Anweisungen entfernt wurden. Für mich geht das.

Ist flüchtiger Ersatz für die Synchronisation? Erreicht es Atomizität?

In Bezug auf die Speichersynchronisation wirft volatile die gleichen Speicherbarrieren wie ein synchronized-Block auf, außer dass die volatile-Barriere unidirektional oder bidirektional ist. volatile reads werfen eine Lastsperre auf, während Schreibvorgänge eine Store-Barriere aufwerfen. Ein synchronized-Block ist eine bidirektionale Barriere.

In Bezug auf atomicity lautet die Antwort jedoch "es kommt darauf an". Wenn Sie einen Wert aus einem Feld lesen oder schreiben, sorgt volatile für die richtige Atomizität. Das Inkrementieren eines volatile-Felds hat jedoch die Einschränkung, dass ++ tatsächlich 3 Operationen ist: Lesen, Inkrementieren, Schreiben. In diesem Fall oder in komplexeren Mutex-Fällen kann ein vollständiger synchronized-Block erforderlich sein.

2
Gray

volatile führt abhängig von der JVM und dem Compiler nicht unbedingt zu großen Änderungen. In vielen (Edge-) Fällen kann es jedoch der Unterschied sein, dass durch die Optimierung die Änderungen einer Variablen nicht wahrgenommen werden und nicht richtig geschrieben werden.

Grundsätzlich kann sich ein Optimierer entscheiden, nichtflüchtige Variablen in Register oder auf den Stack zu setzen. Wenn ein anderer Thread sie im Heap oder in den Primitiven der Klassen ändert, sucht der andere Thread auf dem Stapel nach ihm, und er ist veraltet.

volatile sorgt dafür, dass solche Optimierungen nicht stattfinden, und alle Lese- und Schreibvorgänge erfolgen direkt auf dem Heap oder an einem anderen Ort, wo alle Threads sie sehen werden.

2
Andrey Akhmetov

Die Lösung finden Sie unten.

Der Wert dieser Variablen wird niemals Thread-lokal zwischengespeichert: Alle Lese- und Schreibvorgänge werden direkt in den "Hauptspeicher" verschoben. Die flüchtigen Elemente zwingen den Thread, die ursprüngliche Variable jedes Mal zu aktualisieren. 

public class VolatileDemo {

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {

        ChangeMaker changeMaker = new ChangeMaker();
        changeMaker.start();

        ChangeListener changeListener = new ChangeListener();
        changeListener.start();

    }

    static class ChangeMaker extends Thread {

        @Override
        public void run() {
            while (MY_INT < 5){
                System.out.println("Incrementing MY_INT "+ ++MY_INT);
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }
    }

    static class ChangeListener extends Thread {

        int local_value = MY_INT;

        @Override
        public void run() {
            while ( MY_INT < 5){
                if( local_value!= MY_INT){
                    System.out.println("Got Change for MY_INT "+ MY_INT);
                    local_value = MY_INT;
                }
            }
        }
    }

}

Bitte beziehen Sie sich auf diesen Link http://Java.dzone.com/articles/Java-volatile-keyword-0 , um mehr Klarheit zu erhalten.

1
Azhaguvel A

Objekte, die als flüchtig deklariert sind, werden normalerweise verwendet, um Statusinformationen zwischen Threads zu übermitteln. Um sicherzustellen, dass CPU-Caches aktualisiert werden, d. H., In Gegenwart flüchtiger Felder, eine CPU-Anweisung, eine Speicherbarriere, die auch als Membar oder bezeichnet wird, synchron gehalten werden fence, wird ausgegeben, um CPU-Caches mit einer Änderung des Werts eines flüchtigen Felds zu aktualisieren.

Der volatile-Modifizierer teilt dem Compiler mit, dass die durch volatile modifizierte Variable von anderen Programmteilen unerwartet geändert werden kann.

Die flüchtige Variable darf nur im Thread-Kontext verwendet werden. siehe das Beispiel hier

0
user2554822

Das volatile-Schlüsselwort teilt der JVM mit, dass es möglicherweise von einem anderen Thread geändert wird. Jeder Thread verfügt über einen eigenen Stack und somit über eine eigene Kopie von Variablen, auf die er zugreifen kann. Wenn ein Thread erstellt wird, kopiert er den Wert aller zugänglichen Variablen in seinen eigenen Speicher. 

public class VolatileTest {
    private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {
        new ChangeListener().start();
        new ChangeMaker().start();
    }

    static class ChangeListener extends Thread {
        @Override
        public void run() {
            int local_value = MY_INT;
            while ( local_value < 5){
                if( local_value!= MY_INT){
                    LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
                     local_value= MY_INT;
                }
            }
        }
    }

    static class ChangeMaker extends Thread{
        @Override
        public void run() {

            int local_value = MY_INT;
            while (MY_INT <5){
                LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

probieren Sie dieses Beispiel mit und ohne Volatilität aus.

Viele großartige Beispiele, aber ich möchte hinzufügen, dass es eine Reihe von Szenarien gibt, in denen volatile erforderlich ist, sodass es kein konkretes Beispiel gibt, um sie zu regieren.

  1. Sie können volatile verwenden, um zu erzwingen, dass alle Threads den neuesten Wert der Variablen aus dem Hauptspeicher abrufen.
  2. Sie können synchronization verwenden, um kritische Daten zu schützen
  3. Sie können die Lock API verwenden
  4. Sie können Atomic Variablen verwenden

Weitere Informationen finden Sie unter Flüchtige Java-Beispiele .

0
Johnny