Umfassendes Verständnis volatiler Schlüsselwörter
1.volatil und Sichtbarkeit
Wir alle wissen, dass volatile Sichtbarkeit garantieren kann, aber wie wird sie gewährleistet?
Dies hängt mit dem Happen-before-Prinzip zusammen. Die dritte Bestimmung dieses Prinzips lautet: Für eine flüchtig geänderte Variable sollte der Schreibvorgang früher erfolgen als der Lesevorgang der Variablen. Die spezifischen Schritte sind wie folgt:
Thread A liest die gemeinsam genutzten Variablen in den Arbeitsspeicher, und Thread B liest auch die gemeinsam genutzten Variablen in den Arbeitsspeicher.
Nachdem Thread A die gemeinsam genutzte Variable geändert hat, wird sie sofort im Hauptspeicher aktualisiert. Zu diesem Zeitpunkt wird die gemeinsam genutzte Variable im Arbeitsspeicher von Thread B auf ungültig gesetzt und der neue Wert benötigt erneut aus dem Hauptspeicher gelesen werden. Hardwarebedingt ist die Cache-Zeile der CPU auf einen ungültigen Zustand gesetzt.
Dies stellt die Sichtbarkeit sicher. Nachdem ein Thread die flüchtig geänderte Variable geändert und im Hauptspeicher aktualisiert hat, werden die gemeinsam genutzten Variablen im Arbeitsspeicher anderer Threads ungültig und müssen aus dem abgerufen werden Hauptspeicher. Lesen Sie dies noch einmal.
2. Volatilität und Ordnung
Wir alle wissen, dass Volatilität Ordnung garantieren kann. Wie wird sie also gewährleistet?
volatile sorgt für Ordnung und ist relativ einfach. Es verhindert, dass die JVM und der Prozessor Anweisungen für durch das Schlüsselwort volatile geänderte Variablen neu anordnen. Die Variablen vor oder nach der Variablen können jedoch beliebig angeordnet werden, solange das Endergebnis erhalten bleibt ist das gleiche wie das der Variablen. Halten Sie die Ergebnisse einfach vor der Änderung konsistent.
Grundprinzip
Variablen, die durch volatile geändert werden, wird unten ein „lock:“ vorangestellt. Die Anweisung mit dem „lock“-Präfix entspricht Eine Speicherbarriere. Dies ist genau der Schlüssel zur Gewährleistung von Sichtbarkeit und Ordnung. Die Hauptfunktionen dieser Barriere sind folgende:
Wenn Anweisungen neu angeordnet werden, kann der Code vor der Barriere nicht neu angeordnet werden, und dies auch nicht den Code nach der Barriere neu anordnen.
Stellen Sie beim Ausführen einer Speicherbarriere sicher, dass alle vorherigen Codes ausgeführt wurden und die Ausführungsergebnisse für den Code hinter der Barriere sichtbar sind.
Erzwingen Sie, dass Variablen im Arbeitsspeicher in den Hauptspeicher geleert werden.
Die Variablen im Arbeitsspeicher anderer Threads werden auf ungültig gesetzt und müssen erneut aus dem Hauptspeicher gelesen werden.
3. Flüchtigkeit und Atomizität
Wir alle wissen, dass flüchtige Stoffe keine Atomizität garantieren können. Warum kann sie also keine Atomizität garantieren?
Codedemonstration:
package com.github.excellent01; import java.util.concurrent.CountDownLatch; /** * @auther plg * @date 2019/5/19 9:37 */ public class TestVolatile implements Runnable { private volatile Integer num = 0; private static CountDownLatch latch = new CountDownLatch(10); @Override public void run() { for(int i = 0; i < 1000; i++){ num++; } latch.countDown(); } public Integer getNum() { return num; } public static void main(String[] args) throws InterruptedException { TestVolatile test = new TestVolatile(); for(int i = 0; i < 10; i++){ new Thread(test).start(); } latch.await(); System.out.println(test.getNum()); } }
Starten Sie 10 Threads, jeder Thread fügt die gemeinsam genutzte Variable num 1000 Mal hinzu, und wenn die Ausführung aller Threads abgeschlossen ist, drucken Sie das Endergebnis von num aus.
Es gibt selten 10.000. Dies liegt daran, dass Volatilität keine Atomizität garantieren kann.
Ursachenanalyse:
Die Operation von num++ besteht aus drei Schritten:
Num aus dem Hauptspeicher in den Arbeitsspeicher einlesen
Fügen Sie eins zum Arbeitsspeicher hinzu
Nachdem das Inkrement abgeschlossen ist, schreiben Sie es zurück in den Hauptspeicher.
Obwohl diese drei Schritte alle atomare Operationen sind, sind sie zusammen keine atomaren Operationen und jeder Schritt kann während der Ausführung unterbrochen werden.
Angenommen, der Wert von num ist zu diesem Zeitpunkt 10. Zu diesem Zeitpunkt liest Thread A auch num in seinen eigenen Arbeitsspeicher Zu diesem Zeitpunkt ist der Wert ebenfalls 10. .Thread B ändert den Wert von num in seinem eigenen Arbeitsspeicher und ändert ihn in 11, wurde jedoch zu diesem Zeitpunkt noch nicht im Hauptspeicher aktualisiert, sodass Thread A den Wert nicht kennt von num hat sich geändert, nachdem die flüchtige Variable geändert wurde. Voraussetzung ist, dass sie zuerst im Hauptspeicher aktualisiert wird. Zu diesem Zeitpunkt legen andere Threads den Wert fest Variable, an der sie arbeiten, auf ungültig setzen. Da es nicht im Hauptspeicher aktualisiert wurde, wusste A es dummerweise nicht und fügte eins zu 10 hinzu. Obwohl beide Threads am Ende eine Inkrementierungsoperation durchführten, wurde das Endergebnis daher nur einmal hinzugefügt.
Aus diesem Grund kann volatile keine Atomizität garantieren.
Verwendungsszenarien von Volatile
Gemäß den Eigenschaften von Volatilität sind Reihenfolge und Sichtbarkeit garantiert, die Atomizität ist jedoch nicht garantiert, sodass Volatilität für diejenigen verwendet werden kann, die dies tun erfordert keine Atomizität oder wenn die Atomizität garantiert wurde:
Code-Demonstration
volatile boolean shutdownRequested public void shutdown() { shutdownRequested = true; } public void work() { while(shutdownRequested) { //do stuff } }
Solange der Thread „shutdownRequested“ ändert, wird der Thread, der die Arbeit ausführt, dies sofort sehen und daher sofort anhalten. Wenn nicht, wenn flüchtig hinzugefügt wird, ist es immer wahr, wenn Daten aus dem Arbeitsspeicher gelesen werden, und wird kontinuierlich ausgeführt, ohne zu wissen, dass jemand anderes es gestoppt hat.
Codedemonstration:
package com.github.excellent; import java.util.concurrent.ThreadPoolExecutor; /** * 启动线程会被阻塞,flag 从内存读入,会存入寄存器中,下次直接从寄存器取值 * 因此值一直是false * 即使别的线程已经将值更改了,它也不知道 * 加volatile即可。也可以加锁,只要保证内存可见性即可 * @auther plg * @date 2019/5/2 22:40 */ public class Testvolatile { public static boolean flag = false; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(()->{ for(;;) { System.out.println(flag); } }); Thread thread2 = new Thread(()->{ for(;;){ flag = true; } }); thread1.start(); Thread.sleep(1000); thread2.start(); } }
Ausführungsergebnis:
Es ist so dumm, ohne es zu wissen, und es wird immer noch ausgegeben FALSCH. Fügen Sie einfach einen flüchtigen Stoff hinzu und es ist in Ordnung.
Das obige ist der detaillierte Inhalt vonTiefes Verständnis des volatilen Schlüsselworts. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!