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 .