Ich lese über flüchtige Schlüsselwörter in Java und verstehe den theoretischen Teil davon vollständig.
Was ich aber suche, ist ein gutes Fallbeispiel, das zeigt, was passieren würde, wenn die Variable nicht flüchtig wäre und wenn es so wäre.
Das folgende Code-Snippet funktioniert nicht wie erwartet (von hier aus ):
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 der Thread keepRunning
nicht flüchtig ist , sollte er im Idealfall unbegrenzt weiterlaufen. Aber es hört nach ein paar Sekunden auf.
Ich habe zwei grundlegende Fragen:
- Kann jemand volatile mit Beispiel erklären? Nicht mit der Theorie von JLS.
- Ist flüchtiger Ersatz für Synchronisation? Erreicht es Atomizität?
volatile
garantiert, dass die Änderung am Feld sichtbar ist. Ohne das Schlüsselwort gibt es einfach überhaupt keine Garantien, alles kann passieren; Sie können nicht angeben, dass der Thread weiter ausgeführt werden soll [...] .Antworten:
Flüchtig -> Garantiert Sichtbarkeit und NICHT Atomizität
Synchronisation (Sperren) -> Garantiert Sichtbarkeit und Atomizität (wenn richtig gemacht)
Flüchtig ist kein Ersatz für die Synchronisation
Verwenden Sie flüchtig nur, wenn Sie die Referenz aktualisieren und keine anderen Vorgänge 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.
Nun, das hängt von verschiedenen Umständen ab. In den meisten Fällen ist JVM intelligent genug, um den Inhalt zu leeren.
Richtige Verwendung von flüchtigen Stoffen verschiedene mögliche Verwendungen von flüchtigen Bestandteilen. Die korrekte Verwendung von flüchtig ist schwierig. Ich würde sagen "Wenn Sie Zweifel haben, lassen Sie es weg", verwenden Sie stattdessen einen synchronisierten Block.
Ebenfalls:
Der synchronisierte Block kann anstelle von flüchtig verwendet werden, aber die Umkehrung ist nicht wahr .
quelle
atomicity
Ein Teil der Antwort ist verwirrend. Durch die Synchronisierung erhalten Sie gegenseitigen exklusiven Zugriff und Transparenz .volatile
gibt nur Sichtbarkeit . Auchvolatile
macht Lesen / Schreiben fürlong
unddouble
Atom (Synchronisation tut es auch durch ihre gegenseitige Ausschließlichkeit).Für Ihr spezielles Beispiel: Wenn die Server-JVM nicht als flüchtig deklariert wird, kann sie die
keepRunning
Variable aus der Schleife herausheben, da sie in der Schleife nicht geändert wird (wodurch sie in eine Endlosschleife umgewandelt wird), die Client-JVM jedoch nicht. Deshalb sehen Sie unterschiedliche Ergebnisse.Es folgt eine allgemeine Erklärung zu flüchtigen Variablen:
Wenn ein Feld deklariert wird
volatile
, werden der Compiler und die Laufzeit darauf hingewiesen, dass diese Variable gemeinsam genutzt wird und dass Operationen daran 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. Daher gibt ein Lesevorgang einer flüchtigen Variablen 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.
Die häufigste Verwendung für flüchtige Variablen ist die Vervollständigung, Unterbrechung oder das Statusflag:
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 flüchtig nicht stark genug, um die Inkrementoperation (
count++
) atomar zu machen, es sei denn, Sie können garantieren, dass die Variable nur von einem einzelnen Thread geschrieben wird.Das Verriegeln kann sowohl Sichtbarkeit als auch Atomizität garantieren. flüchtige Variablen können nur die Sichtbarkeit garantieren.
Sie können flüchtige Variablen nur verwenden, wenn alle folgenden Kriterien erfüllt sind:
Debugging-Tipp :
-server
Geben Sie beim Aufrufen der JVM auch beim Entwickeln und Testen immer den JVM-Befehlszeilenschalter an. Die Server-JVM führt mehr Optimierungen durch als die Client-JVM, z. B. das Hochheben 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 Bereitstellungsumgebung (Server-JVM) beschädigt werden.Dies ist ein Auszug aus "Java Concurrency in Practice" , dem besten Buch, das Sie zu diesem Thema finden können.
quelle
Ich habe Ihr Beispiel leicht 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); } }
quelle
votalite
hierWas ist ein flüchtiges Schlüsselwort?
Betrachten Sie den Code zunächst 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(); } }
Im Idealfall sollte dieses Programm
print hello
bisRETURN key
gedrückt werden. Essome machines
kann jedoch vorkommen, dass eine Variable ausgeführt wirdcached
und Sie ihren Wert nicht über die Methode shutdown () ändern können, die zuminfinite
Drucken von Hallo-Text führt.So flüchtiges Schlüsselwort verwendet wird , ist es ,
guaranteed
dass Ihr Variable wird nicht im Cache gespeichert wird, wird alsorun fine
aufall machines
.private volatile boolean running = true; //volatile keyword
Die Verwendung eines flüchtigen Schlüsselworts ist also a
good
undsafer programming practice
.quelle
Variable Volatile
: Das flüchtige Schlüsselwort gilt für Variablen. Das Schlüsselwort volatile in Java garantiert, dass der Wert der flüchtigen Variablen immer aus dem Hauptspeicher und nicht aus dem lokalen Cache des Threads gelesen wird.Access_Modifier volatile DataType Variable_Name;
Flüchtiges Feld: Ein Hinweis für die VM, dass mehrere Threads gleichzeitig versuchen können, auf den Wert des Felds zuzugreifen / ihn zu aktualisieren. Zu einer speziellen Art von Instanzvariablen, die von allen Threads mit geändertem Wert gemeinsam genutzt werden müssen. Ähnlich wie bei der statischen Variablen (Klasse) wird nur eine Kopie des flüchtigen Werts im Hauptspeicher zwischengespeichert, sodass jeder Thread vor der Ausführung von ALU-Operationen den aktualisierten Wert nach der ALU-Operation aus dem Hauptspeicher lesen muss, um in den Hauptspeicher zu schreiben. (Ein Schreibvorgang in eine flüchtige Variable v wird mit allen nachfolgenden Lesevorgängen von v durch einen beliebigen Thread synchronisiert.) Dies bedeutet, dass Änderungen an einer flüchtigen Variablen für andere Threads immer sichtbar sind.
Hier ein ,
nonvoltaile variable
wenn der Thread T1 den Wert in t1-Cache ändert, kann der Thread T2 nicht die geänderten Wert , bis t1 schreibt zugreifen, t2 aus dem Hauptspeicher für den letzten geänderten Wert lesen, die führenData-Inconsistancy
.Shared Variables
: Speicher, der von Threads gemeinsam genutzt werden kann, wird als gemeinsamer Speicher oder Heapspeicher bezeichnet. Alle Instanzfelder, statischen Felder und Array-Elemente werden im Heapspeicher gespeichert.Synchronisation : Synchronisiert gilt für Methoden und Blöcke. Ermöglicht die gleichzeitige Ausführung von jeweils nur einem Thread für ein Objekt. Wenn t1 die Kontrolle übernimmt, müssen die verbleibenden Threads warten, bis die Kontrolle freigegeben wird.
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(); } } }
@sehen
quelle
Wenn Sie mit einem einzelnen Prozessor arbeiten oder wenn Ihr System sehr ausgelastet ist, tauscht das Betriebssystem möglicherweise die Threads aus, was zu einer gewissen Cache-Ungültigkeit führt. Ein nicht zu haben
volatile
bedeutet nicht, dass das Gedächtnis nicht wird geteilt werden, aber die JVM nicht synchronisieren Speicher versucht , wenn es aus Performance - Gründen kann so dass der Speicher nicht aktualisiert werden kann.Eine andere Sache zu beachten ist, dass
System.out.println(...)
synchronisiert wird, weil der BasiswertPrintStream
synchronisiert, um überlappende Ausgaben zu stoppen. Sie erhalten also die Speichersynchronisation "kostenlos" im Haupt-Thread. Dies erklärt jedoch immer noch nicht, warum die Leseschleife die Aktualisierungen überhaupt sieht.println(...)
Unabhängig davon, ob die Leitungen ein- oder ausgehen, dreht sich Ihr Programm für mich unter Java6 auf einem MacBook Pro mit einem Intel i7.Ich finde dein Beispiel gut. Ich bin mir nicht sicher, warum es nicht funktioniert, wenn alle
System.out.println(...)
Anweisungen entfernt wurden. Für mich geht das.In Bezug auf die Speichersynchronisation
volatile
werden die gleichen Speicherbarrieren wie bei einemsynchronized
Block ausgelöst, außer dass dievolatile
Barriere unidirektional oder bidirektional ist.volatile
Lesevorgänge werfen eine Lastsperre auf, während Schreibvorgänge eine Speicherbarriere aufwerfen. Einsynchronized
Block ist eine bidirektionale Barriere mit zusätzlicher Mutex-Verriegelung.In Bezug
atomicity
auf lautet die Antwort jedoch "es kommt darauf an". Wenn Sie einen Wert aus einem Feld lesen oder schreiben, erhalten Sievolatile
die richtige Atomizität. Das Inkrementieren einesvolatile
Feldes leidet jedoch unter der Einschränkung, dass++
es sich tatsächlich um drei Operationen handelt: Lesen, Inkrementieren, Schreiben. In diesem Fall oder in komplexeren Mutex-Fällen kann ein vollständigersynchronized
Block erforderlich sein.AtomicInteger
löst das++
Problem mit einer komplizierten Test-and-Set-Spin-Schleife.quelle
synchronized
odervolatile
) überschritten wird, besteht für den gesamten Speicher eine "Vorher-passiert" -Beziehung. Es gibt keine Garantie für die Reihenfolge der Sperren und die Synchronisierung, es sei denn, Sie sperren denselben Monitor, auf den Sie als @BrunoReis verwiesen werden. Wenn derprintln(...)
Vorgang abgeschlossen ist, wird garantiert, dass daskeepRunning
Feld aktualisiert wird.Wenn eine Variable ist
volatile
, wird garantiert, dass sie nicht zwischengespeichert wird und dass verschiedene Threads den aktualisierten Wert sehen. Eine Nichtmarkierungvolatile
garantiert jedoch nicht das Gegenteil.volatile
war eines dieser Dinge, die in der JVM lange Zeit kaputt waren und immer noch nicht immer gut verstanden wurden.quelle
volatile
wird nicht unbedingt riesige Änderungen verursachen, abhängig von der JVM und dem Compiler. In vielen (Rand-) Fällen kann es jedoch der Unterschied zwischen der Optimierung sein, die dazu führt, dass die Änderungen einer Variablen nicht bemerkt werden, anstatt dass sie korrekt geschrieben werden.Grundsätzlich kann ein Optimierer wählen, nichtflüchtige Variablen in Register oder auf den Stapel zu setzen. Wenn ein anderer Thread sie im Heap oder in den Grundelementen der Klassen ändert, sucht der andere Thread im Stapel weiter danach und es ist veraltet.
volatile
stellt sicher, dass solche Optimierungen nicht stattfinden und alle Lese- und Schreibvorgänge direkt auf den Heap oder einen anderen Ort erfolgen, an dem alle Threads sie sehen.quelle
Viele gute Beispiele, aber ich möchte nur hinzufügen, dass es eine Reihe von Szenarien gibt, in denen dies
volatile
erforderlich ist, sodass es kein konkretes Beispiel gibt, um sie zu regieren.volatile
, um alle Threads zu zwingen, den neuesten Wert der Variablen aus dem Hauptspeicher abzurufen.synchronization
kritische Daten schützenLock
API verwendenAtomic
Variablen verwendenWeitere Beispiele für flüchtige Java-Beispiele finden Sie hier .
quelle
Nachfolgend finden Sie die Lösung:
Der Wert dieser Variablen wird niemals threadlokal zwischengespeichert: Alle Lese- und Schreibvorgänge werden direkt in den "Hauptspeicher" verschoben. Die flüchtige Kraft zwingt 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 verweisen Sie auf diesen Link http://java.dzone.com/articles/java-volatile-keyword-0 , um mehr Klarheit zu erhalten.
quelle
Das flüchtige Schlüsselwort teilt der JVM mit, dass es möglicherweise von einem anderen Thread geändert wird. Jeder Thread hat einen eigenen Stapel und somit eine eigene Kopie der 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 flüchtig aus.
quelle
Objekte, die als flüchtig deklariert sind, werden normalerweise verwendet, um Statusinformationen zwischen Threads zu kommunizieren. Um sicherzustellen, dass die CPU-Caches aktualisiert werden, dh synchron gehalten werden, wenn flüchtige Felder vorhanden sind, eine CPU-Anweisung, eine Speicherbarriere, die häufig als Membar oder bezeichnet wird Zaun, wird ausgegeben, um CPU-Caches mit einer Änderung des Werts eines flüchtigen Feldes zu aktualisieren.
Der flüchtige Modifikator teilt dem Compiler mit, dass die durch flüchtig modifizierte Variable von anderen Teilen Ihres Programms unerwartet geändert werden kann.
Die flüchtige Variable darf nur im Thread-Kontext verwendet werden. siehe das Beispiel hier
quelle