801 Stimmen

Wofür ist das Schlüsselwort volatile nützlich?

Bei der Arbeit stieß ich heute auf die volatile Schlüsselwort in Java. Da ich damit nicht sehr vertraut bin, fand ich diese Erklärung .

Wenn man bedenkt, wie detailliert in diesem Artikel das betreffende Schlüsselwort erklärt wird, haben Sie es dann jemals verwendet oder könnten Sie sich einen Fall vorstellen, in dem Sie dieses Schlüsselwort auf die richtige Art und Weise verwenden könnten?

9voto

CJay Punkte 563

Angenommen, ein Thread ändert den Wert einer gemeinsamen Variablen, wenn Sie nicht volatile Modifikator für diese Variable. Wenn andere Threads den Wert dieser Variable lesen wollen, sehen sie den aktualisierten Wert nicht, weil sie den Wert der Variable aus dem Cache der CPU und nicht aus dem RAM-Speicher lesen. Dieses Problem ist auch bekannt als Visibility Problem .

Durch die Deklaration der gemeinsamen Variablen volatile werden alle Schreibzugriffe auf die Zählervariable sofort in den Hauptspeicher zurückgeschrieben. Außerdem werden alle Lesevorgänge der Zählervariablen direkt aus dem Hauptspeicher gelesen.

public class SharedObject {
    public volatile int sharedVariable = 0;
}

Bei nichtflüchtigen Variablen gibt es keine Garantien darüber, wann die Java Virtual Machine (JVM) Daten aus dem Hauptspeicher in die CPU-Caches liest oder Daten aus den CPU-Caches in den Hauptspeicher schreibt. Dies kann verschiedene Probleme verursachen, die ich in den folgenden Abschnitten erläutern werde.


Beispiel:

Stellen Sie sich eine Situation vor, in der zwei oder mehr Threads Zugriff auf ein gemeinsames Objekt haben, das eine wie folgt deklarierte Zählervariable enthält:

public class SharedObject {
    public int counter = 0;
}

Stellen Sie sich auch vor, dass nur Thread 1 die Zählervariable inkrementiert, aber sowohl Thread 1 als auch Thread 2 die Zählervariable von Zeit zu Zeit lesen können.

Wenn die Zählervariable nicht als flüchtig deklariert ist, gibt es keine Garantie dafür, wann der Wert der Zählervariable vom CPU-Cache zurück in den Hauptspeicher geschrieben wird. Das bedeutet, dass der Wert der Zählervariablen im CPU-Cache möglicherweise nicht mit dem Wert im Hauptspeicher übereinstimmt. Diese Situation wird hier veranschaulicht:

volatile

Das Problem, dass Threads den neuesten Wert einer Variablen nicht sehen, weil er noch nicht von einem anderen Thread in den Hauptspeicher zurückgeschrieben wurde, wird als "Sichtbarkeitsproblem" bezeichnet. Die Aktualisierungen eines Threads sind für andere Threads nicht sichtbar.

0 Stimmen

Aktualisiert der Hauptthread (Parent-Thread) im Allgemeinen alles direkt im Speicher? Oder hat der Hauptthread auch einen Cache

0 Stimmen

Auch im Allgemeinen (Nicht-Multithreading-Szenarien) in Java, wann wird der Ram aus dem Cache aktualisiert?

5voto

fatih tekin Punkte 951

volatile garantiert nur, dass alle Threads, auch sie selbst, inkrementieren. Zum Beispiel: ein Zähler sieht die gleiche Seite der Variablen zur gleichen Zeit. Es wird nicht anstelle von "synchronized" oder "atomic" oder anderen Dingen verwendet, es macht die Lesevorgänge vollständig synchronisiert. Bitte vergleichen Sie es nicht mit anderen Java-Schlüsselwörtern. Wie das folgende Beispiel zeigt, sind Operationen mit flüchtigen Variablen ebenfalls atomar, sie schlagen fehl oder sind 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);
        }
    }
}

Auch wenn Sie flüchtige oder nicht flüchtige Daten eingeben, werden die Ergebnisse immer unterschiedlich sein. Aber wenn Sie AtomicInteger wie unten verwenden, werden die Ergebnisse immer gleich sein. Dies ist das gleiche mit synchronisierten auch.

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

5voto

Rudi Adianto Punkte 247

Wenn Sie eine Multithreading-Anwendung entwickeln, müssen Sie das Schlüsselwort "volatile" oder "synchronized" sowie alle anderen Tools und Techniken zur Steuerung der Gleichzeitigkeit verwenden, die Ihnen zur Verfügung stehen. Ein Beispiel für eine solche Anwendung sind Desktop-Anwendungen.

Wenn Sie eine Anwendung entwickeln, die auf einem Anwendungsserver (Tomcat, JBoss AS, Glassfish usw.) bereitgestellt werden soll, müssen Sie sich nicht selbst um die Gleichzeitigkeitskontrolle kümmern, da diese bereits vom Anwendungsserver übernommen wird. Wenn ich mich richtig erinnere, verbietet der Java EE-Standard sogar jegliche Gleichzeitigkeitskontrolle in Servlets und EJBs, da sie Teil der "Infrastruktur"-Schicht ist, die von der Handhabung befreit werden soll. Gleichzeitigkeitskontrolle ist in solchen Anwendungen nur möglich, wenn Sie Singleton-Objekte implementieren. Dies ist sogar schon angesprochen, wenn Sie Ihre Komponenten mit einem Framework 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 wird, ist die Verwendung von "volatile" nicht erforderlich.

5voto

Abhishek Luthra Punkte 2225

Während ich in den hier erwähnten Antworten viele gute theoretische Erklärungen sehe, füge ich hier ein praktisches Beispiel mit einer Erklärung hinzu:

1.

CODE OHNE FLÜCHTIGE VERWENDUNG AUSFÜHREN

public class VisibilityDemonstration {

private static int sCount = 0;

public static void main(String[] args) {
    new Consumer().start();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        return;
    }
    new Producer().start();
}

static class Consumer extends Thread {
    @Override
    public void run() {
        int localValue = -1;
        while (true) {
            if (localValue != sCount) {
                System.out.println("Consumer: detected count change " + sCount);
                localValue = sCount;
            }
            if (sCount >= 5) {
                break;
            }
        }
        System.out.println("Consumer: terminating");
    }
}

static class Producer extends Thread {
    @Override
    public void run() {
        while (sCount < 5) {
            int localValue = sCount;
            localValue++;
            System.out.println("Producer: incrementing count to " + localValue);
            sCount = localValue;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                return;
            }
        }
        System.out.println("Producer: terminating");
    }
}
}

In dem obigen Code gibt es zwei Threads - Producer und Consumer.

Der Producer-Thread durchläuft die Schleife 5 Mal (mit einem Ruhezustand von 1000 milliSekunden oder 1 Sekunde) dazwischen. In jeder Iteration erhöht der Producer-Thread den Wert der Variable sCount um 1. Der Producer ändert also den Wert von sCount in allen Iterationen von 0 auf 5

Der Verbraucher-Thread befindet sich in einer konstanten Schleife und druckt jedes Mal, wenn sich der Wert von sCount ändert, bis der Wert 5 erreicht ist, wo er endet.

Beide Schleifen werden zur gleichen Zeit gestartet. Daher sollten sowohl der Produzent als auch der Konsument den Wert von sCount 5 Mal drucken.

OUTPUT

Consumer: detected count change 0
Producer: incrementing count to 1
Producer: incrementing count to 2
Producer: incrementing count to 3
Producer: incrementing count to 4
Producer: incrementing count to 5
Producer: terminating

ANALYSE

Wenn im obigen Programm der Producer-Thread den Wert von sCount aktualisiert, aktualisiert er den Wert der Variablen im Hauptspeicher (Speicher, aus dem jeder Thread den Wert der Variablen anfänglich lesen wird). Der Consumer-Thread liest den Wert von sCount jedoch nur das erste Mal aus diesem Hauptspeicher und speichert den Wert dieser Variablen dann in seinem eigenen Speicher. Selbst wenn der ursprüngliche Wert von sCount im Hauptspeicher vom Producer-Thread aktualisiert wurde, liest der Consumer-Thread den Wert aus seinem Cache, der nicht aktualisiert wurde. Dies wird als SICHTBARKEITSPROBLEM .

2.

CODE MIT FLÜCHTIGER VERWENDUNG AUSFÜHREN

Ersetzen Sie im obigen Code die Zeile, in der sCount deklariert wird, durch den folgenden Code:

private volatile  static int sCount = 0;

OUTPUT

Consumer: detected count change 0
Producer: incrementing count to 1
Consumer: detected count change 1
Producer: incrementing count to 2
Consumer: detected count change 2
Producer: incrementing count to 3
Consumer: detected count change 3
Producer: incrementing count to 4
Consumer: detected count change 4
Producer: incrementing count to 5
Consumer: detected count change 5
Consumer: terminating
Producer: terminating

ANALYSE

Wenn wir eine Variable als flüchtig deklarieren, bedeutet das, dass alle Lese- und Schreibzugriffe auf diese Variable oder von dieser Variable direkt in den Hauptspeicher gehen. Die Werte dieser Variablen werden niemals zwischengespeichert.

Da der Wert der Variable sCount von keinem Thread zwischengespeichert wird, liest der Consumer immer den Originalwert von sCount aus dem Hauptspeicher (wo er vom Producer-Thread aktualisiert wird). In diesem Fall ist die Ausgabe also korrekt, wenn beide Threads die verschiedenen Werte von sCount 5 Mal ausgeben.

Auf diese Weise löst das Schlüsselwort volatile das Problem SICHTBARKEITSPROBLEM .

0 Stimmen

Interessanterweise speichert der Verbraucher den Wert nicht zwischen, wenn Sie ihn vor der if-Anweisung ausdrucken. Seltsam

0 Stimmen

Das ist ein großartiges Beispiel!

4voto

tstuber Punkte 352

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

Nur Mitgliedsvariablen können flüchtig oder transient sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X