Heim  >  Artikel  >  Java  >  Analysieren Sie den Beispielcode der Java-Implementierung des flüchtigen Schlüsselworts vom Stammverzeichnis aus (Bild).

Analysieren Sie den Beispielcode der Java-Implementierung des flüchtigen Schlüsselworts vom Stammverzeichnis aus (Bild).

黄舟
黄舟Original
2017-03-22 10:47:231362Durchsuche

1. AnalyseÜbersicht

  1. Verwandte Konzepte des Speichermodells

  2. Drei Konzepte in der gleichzeitigen Programmierung

  3. Java-Speichermodell

  4. Eingehende Analyse desVolatilSchlüsselworts

  5. Szenarien für die Verwendung des flüchtigen Schlüsselworts

2 Konzept

Diese Art von Variable wird normalerweise als gemeinsam genutzte Variable bezeichnet.

Das heißt, wenn eine Variable in mehreren CPUs zwischengespeichert wird Programmierung), dann kann es zu Cache-Inkonsistenzproblemen kommen

Um das Cache-Inkonsistenzproblem zu lösen, gibt es normalerweise zwei Lösungen:

  • Durch Hinzufügen von LOCK# zum Bus

  • Über das Cache-Kohärenzprotokoll

Diese beiden Methoden befinden sich auf Hardwareebene. Die Methode wird auf

Die obige Methode bereitgestellt 1 wird ein Problem haben, da andere CPUs während der Sperrzeit nicht auf den Speicher zugreifen können, was zu Ineffizienz führt.

Das bekannteste Cache-Kohärenzprotokoll ist das MESI-Protokoll von Intel Die in jedem Cache verwendete gemeinsam genutzte Variable ist konsistent: Wenn die CPU Daten schreibt und festgestellt wird, dass sie eine gemeinsam genutzte Variable ist, wird sie auch auf anderen CPUs gelöscht Wenn andere CPUs diese Variable lesen müssen, stellen sie fest, dass die Cache-Zeile, die die Variable in ihrem eigenen Cache speichert, ungültig ist Aus dem Speicher erneut lesen

3. Drei Konzepte in der gleichzeitigen Programmierung

Bei der gleichzeitigen Programmierung stoßen wir normalerweise auf die folgenden drei Probleme: Atomizitätsproblem, Sichtbarkeitsproblem, Bestellproblem

3.1 Atomarität

Atomarität: Ein Prozess, bei dem eine Operation oder mehrere Operationen entweder alle ausgeführt werden und nicht durch irgendwelche Faktoren unterbrochen werden

3.2 Sichtbarkeit

Sichtbarkeit bedeutet, dass ein Thread den geänderten Wert sofort sehen kann

3.3 Ordnung

Ordnung: Das heißt, die Reihenfolge der Programmausführung erfolgt in der Reihenfolge des Codes. Aus der Codesequenz geht hervor, dass Anweisung 1 vor Anweisung 2 liegt. Wenn die JVM dies also tatsächlich ausführt Wird garantiert, dass Anweisung 1 vor Anweisung 2 ausgeführt wird? Warum kann es hier zu einer Duplizierung der Anweisung kommen?

Im Folgenden wird erläutert, was eine Neuordnung von Anweisungen ist. Um die Effizienz des Programmbetriebs zu verbessern, kann der Prozessor nicht garantieren, dass die Ausführungsreihenfolge jeder Anweisung im Programm stimmt Die Reihenfolge ist dieselbe wie im Code, es wird jedoch sichergestellt, dass das endgültige Ausführungsergebnis des Programms mit dem Ergebnis der sequentiellen Ausführung des Codes übereinstimmt.

Die Neuordnung von Anweisungen hat keinen Einfluss auf die Ausführung eines einzelnen Threads, sondern auf die Korrektheit der gleichzeitigen Ausführung von Threads.

Mit anderen Worten: Damit gleichzeitige Programme korrekt ausgeführt werden können, müssen Atomizität, Sichtbarkeit und Ordnung gewährleistet sein. Solange einer davon nicht gewährleistet ist, kann es zu Fehlfunktionen des Programms kommen.

4. Java-Speichermodell

In der Java Virtual Machine Specification wird versucht, ein Java-Speichermodell (Java Memory

Model

, JMM) zu definieren, um verschiedene Unterschiede beim Zugriff auf den Systemspeicher, um konsistente Speicherzugriffseffekte für Java-Programme auf verschiedenen Plattformen zu erzielen. Was legt das Java-Speichermodell fest? Es definiert die Zugriffsregeln für Variablen im Programm. In größerem Umfang definiert es die Reihenfolge der Programmausführung. Beachten Sie, dass das Java-Speichermodell zur Erzielung einer besseren Ausführungsleistung weder die Ausführungs-Engine daran hindert, die Register oder den Cache des Prozessors zu verwenden, um die Geschwindigkeit der Befehlsausführung zu verbessern, noch den Compiler daran hindert, Anweisungen neu anzuordnen. Mit anderen Worten, im Java-Speichermodell wird es auch Probleme mit der Cache-Konsistenz und der Neuordnung von Befehlen geben.

Das Java-Speichermodell legt fest, dass alle Variablen im Hauptspeicher gespeichert werden (ähnlich dem zuvor erwähnten physischen Speicher) und jeder Thread über einen eigenen Arbeitsspeicher verfügt (ähnlich dem vorherigen Cache). Alle Operationen an Variablen durch Threads müssen im Arbeitsspeicher ausgeführt werden und können nicht direkt im Hauptspeicher ausgeführt werden. Und jeder Thread kann nicht auf den Arbeitsspeicher anderer Threads zugreifen.

4.1 Atomarität

In Java sind Lese- und Zuweisungsoperationen zu Variablen des Basisdatentyps

atomare Operationen, das heißt, diese Operationen können weder unterbrochen noch ausgeführt werden oder nicht ausgeführt.

Bitte analysieren Sie, welche der folgenden Operationen atomare Operationen sind:

x = 10; //Anweisung 1

  1. y = x; //Aussage 2

  2. x++; //Aussage 3

  3. x = x + 1; //Aussage 4

Tatsächlich ist nur Anweisung 1 eine atomare Operation, und die anderen drei Anweisungen sind keine atomaren Operationen.

Mit anderen Worten, nur einfaches Lesen und Zuweisen (und die Zahl muss einer Variablen zugewiesen werden, die gegenseitige Zuweisung zwischen Variablen ist keine atomare Operation) sind atomare Operationen.

Wie aus dem Obigen hervorgeht, garantiert das Java-Speichermodell nur, dass grundlegende Lese- und Zuweisungsoperationen atomare Operationen sind. Wenn Sie Atomizität für einen größeren Bereich von Operationen erreichen möchten, können Sie dies durch synchronisierte und Sperren erreichen .

4.2 Sichtbarkeit

Für die Sichtbarkeit stellt Java das Schlüsselwort volatile zur Verfügung, um die Sichtbarkeit sicherzustellen.

Wenn eine gemeinsam genutzte Variable flüchtig geändert wird, wird sichergestellt, dass der geänderte Wert sofort im Hauptspeicher aktualisiert wird. Wenn andere Threads ihn lesen müssen, wird der neue Wert aus dem Speicher gelesen.

Gewöhnliche gemeinsam genutzte Variablen können die Sichtbarkeit nicht garantieren, da nach der Änderung einer gewöhnlichen gemeinsam genutzten Variablen ungewiss ist, wann sie in den Hauptspeicher geschrieben wird. Wenn andere Threads sie lesen, ist der Speicher möglicherweise immer noch der ursprüngliche alte Wert Daher ist die Sichtbarkeit nicht garantiert.

Darüber hinaus kann die Sichtbarkeit auch durch synchronisiert und Lock gewährleistet werden. Synchronized und Lock können sicherstellen, dass nur ein Thread gleichzeitig die Sperre erhält und dann den Synchronisationscode ausführt, und die Variable wird geändert in den Hauptspeicher geleert, bevor die Sperre aufgehoben wird. Die Sicht ist somit gewährleistet.

4.3 Ordnung

Im Java-Speichermodell dürfen Compiler und Prozessor Anweisungen neu anordnen, der Neuordnungsprozess hat jedoch keinen Einfluss auf die Ausführung von Single-Thread-Programmen, sondern auf die Richtigkeit der gleichzeitigen Multithread-Ausführung.

In Java können Sie das Schlüsselwort volatile verwenden, um eine gewisse „Ordnung“ sicherzustellen (es kann die Neuordnung von Anweisungen verhindern). Darüber hinaus kann die Ordnung durch Synchronisierung und Sperre sichergestellt werden. Offensichtlich stellen Synchronisierung und Sperre sicher, dass zu jedem Zeitpunkt ein Thread den Synchronisierungscode ausführt.

Darüber hinaus verfügt das Java-Speichermodell über eine gewisse angeborene „Ordnung“, d. Wenn die Ausführungsreihenfolge zweier Operationen nicht aus dem Prinzip „Vorher geschieht“ abgeleitet werden kann, ist ihre Reihenfolge nicht garantiert und die virtuelle Maschine kann sie nach Belieben neu anordnen.

Das Folgende ist eine detaillierte Einführung in das Prinzip „Vorher passiert“:

  1. Regeln für die Programmsequenz: Schreiben Sie innerhalb eines Threads entsprechend der Codereihenfolge vorne die Operation tritt vor dem nach

  2. geschriebenen Vorgang auf. Sperrregel: Ein Entsperrvorgang erfolgt vor demselben Sperrvorgang nach derselben Sperre

  3. flüchtig Variablenregeln: Eine Schreiboperation auf eine Variable erfolgt zuerst, bevor eine nachfolgende Leseoperation auf diese Variable erfolgt

  4. Weitergaberegel: Wenn Operation A zuerst vor Operation B und Operation B ausgeführt wird zuerst vor Operation C, daraus kann geschlossen werden, dass Operation A zuerst vor Operation C ausgeführt wird

  5. Thread-Startregel: Die start()-Methode des Thread-Objekts tritt hier zuerst auf Jede Aktion des Threads

  6. Thread-Unterbrechungsregel: Der Aufruf der Thread-Interrupt()-Methode erfolgt zuerst, wenn der Code des unterbrochenen Threads das Auftreten des Interrupt-Ereignisses erkennt

  7. Thread-Beendigungsregel: Alle Vorgänge in einem Thread werden zuerst ausgeführt, wenn der Thread beendet wird. Wir können anhand der Thread.join()-Methode und des Rückgabewerts von Thread.isAlive() erkennen, dass der Thread beendet wurde

  8. Objektfinalisierungsregeln: Die Initialisierung eines Objekts erfolgt zuerst am Anfang seiner finalize()-Methode

Unter diesen 8 Regeln sind die ersten vier Regeln wichtiger sind und die letzten vier offensichtlich sind.

Lassen Sie uns die ersten 4 Regeln erklären:

  1. Für Programmreihenfolgeregeln verstehe ich, dass die Ausführung eines Teils des Programmcodes so aussieht, als ob er in einem einzelnen Thread erfolgt Befehl. Beachten Sie, dass in dieser Regel zwar erwähnt wird, dass „vorne geschriebene Vorgänge vor hinten geschriebenen Vorgängen erfolgen“, dies jedoch bedeuten sollte, dass die Reihenfolge, in der das Programm scheinbar ausgeführt wird, der Reihenfolge des Codes entspricht, da die virtuelle Maschine möglicherweise ausgeführt wird Operationen am Programmcode neu angeordnet. Obwohl eine Neuordnung durchgeführt wird, stimmt das endgültige Ausführungsergebnis mit dem Ergebnis der sequentiellen Ausführung des Programms überein. Es werden nur Anweisungen neu angeordnet, die keine Datenabhängigkeiten aufweisen. Daher scheint die Programmausführung in einem einzelnen Thread in der richtigen Reihenfolge ausgeführt zu werden, was verständlich sein sollte. Tatsächlich wird diese Regel verwendet, um die Korrektheit der Ergebnisse der Programmausführung in einem einzelnen Thread sicherzustellen, sie kann jedoch nicht die Korrektheit der Programmausführung in mehreren Threads garantieren.

  2. Die zweite Regel ist auch einfacher zu verstehen, das heißt, ob in einem einzelnen Thread oder in mehreren Threads, wenn sich dieselbe Sperre in einem gesperrten Zustand befindet, muss die Sperre sein Nachdem der Freigabevorgang ausgeführt wurde, kann der Sperrvorgang später fortgesetzt werden.

  3. Die dritte Regel ist eine wichtigere Regel und wird im folgenden Artikel im Mittelpunkt stehen. Die intuitive Erklärung ist, dass, wenn ein Thread zuerst eine Variable schreibt und dann ein Thread sie liest, der Schreibvorgang definitiv vor dem Lesevorgang erfolgt.

  4. Die vierte Regel spiegelt tatsächlich die transitive Natur des Prinzips „passiert vorher“ wider.

5. Eingehende Analyse des flüchtigen Schlüsselworts

5.1 Zweistufige Semantik des flüchtigen Schlüsselworts

Einmal eine gemeinsam genutzte Variable (Mitgliedsvariable). einer Klasse, class Nachdem die statische Mitgliedsvariable (Variable) flüchtig geändert wurde, verfügt sie über zwei Semantikebenen:

  1. stellt die Sichtbarkeit sicher, wenn verschiedene Threads mit dieser Variablen arbeiten, also ein Thread Ändert einen bestimmten Wert einer Variablen. Der neue Wert ist für andere Threads sofort sichtbar.

  2. Das Nachbestellen von Anweisungen ist verboten.

Bezüglich der Sichtbarkeit schauen wir uns zunächst einen Codeabschnitt an. Wenn Thread 1 zuerst und Thread 2 später ausgeführt wird:

//线程1
boolean stop = false;
while(!stop){
doSomething();
}

//线程2
stop = true;

Dieser Code ist sehr typisch Viele Leute verwenden möglicherweise diese Markierungsmethode, wenn sie einen Thread unterbrechen. Aber wird dieser Code tatsächlich völlig korrekt ausgeführt? Das heißt, wird der Thread unterbrochen? Nicht unbedingt, vielleicht in den meisten Fällen, kann dieser Code den Thread unterbrechen, aber er kann auch dazu führen, dass der Thread nicht unterbrochen werden kann (obwohl diese Möglichkeit sehr gering ist, aber sobald dies geschieht, wird eine Endlosschleife verursacht).

Im Folgenden wird erklärt, warum dieser Code dazu führen kann, dass der Thread nicht unterbrochen werden kann. Wie bereits erläutert, verfügt jeder Thread während der Ausführung über seinen eigenen Arbeitsspeicher. Wenn also Thread 1 ausgeführt wird, kopiert er den Wert der Stoppvariablen und legt ihn in seinem eigenen Arbeitsspeicher ab.

Wenn dann Thread 2 den Wert der Stoppvariablen ändert, aber bevor er Zeit hat, ihn in den Hauptspeicher zu schreiben, schaltet Thread 2 um, um andere Dinge zu tun, dann kennt Thread 1 die Änderung des Stopps nicht Variable von Thread 2 , sodass der Zyklus fortgesetzt wird.

Aber nach der Verwendung der flüchtigen Änderung wird es anders:

  • Erstens: Die Verwendung des Schlüsselworts volatile erzwingt, dass der geänderte Wert sofort in den Hauptspeicher geschrieben wird >

  • Zweitens: Wenn Sie das Schlüsselwort volatile verwenden und Thread 2 eine Änderung vornimmt, wird die Cache-Zeile des Cache-Variablenstopps im Arbeitsspeicher von Thread 1 ungültig (falls vorhanden). wird auf die Hardwareschicht übertragen, d. h. die entsprechende Cache-Zeile im L1- oder L2-Cache der CPU ist ungültig); Der Speicher von Thread 1 ist ungültig, Thread 1 liest ihn erneut. Der Wert der Variablen stop wird aus dem Hauptspeicher gelesen.

  • Wenn dann Thread 2 den Stoppwert ändert (dazu gehören natürlich zwei Vorgänge, das Ändern des Werts im Arbeitsspeicher von Thread 2 und das anschließende Schreiben des geänderten Werts in den Speicher) Dadurch wird die Cache-Zeile der Cache-Variablen im Arbeitsspeicher von Thread 1 ungültig gemacht. Wenn Thread 1 sie dann liest, stellt er fest, dass seine Cache-Zeile ungültig ist. Er wartet auf die dem Cache entsprechende Hauptspeicheradresse Zeile aktualisiert und dann aus dem entsprechenden Hauptspeicher gelesen.

    Dann liest Thread 1 den neuesten korrekten Wert.
5.2 Garantiert volatile Atomizität?

Volatil garantiert keine Atomizität. Schauen wir uns unten ein Beispiel an.

Jeder denkt über die Ausgabe dieses Programms nach? Vielleicht denken einige Freunde, dass es 10.000 sind. Wenn Sie es jedoch ausführen, werden Sie feststellen, dass die Ergebnisse jedes Mal inkonsistent sind und immer eine Zahl unter 10.000 sind.

Hier liegt ein Missverständnis vor. Das Schlüsselwort volatile kann Sichtbarkeit gewährleisten, aber der Fehler im obigen Programm besteht darin, dass es keine Atomizität gewährleistet. Die Sichtbarkeit kann nur sicherstellen, dass jedes Mal der neueste Wert gelesen wird, Volatilität kann jedoch nicht die Atomizität von Operationen an Variablen garantieren.

public class Test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}
Wie bereits erwähnt, ist die automatische Inkrementierung nicht atomar. Sie umfasst das Lesen des ursprünglichen Werts der Variablen, das Addieren von 1 und das Schreiben in den Arbeitsspeicher. Das bedeutet, dass die drei Unteroperationen der Auto-Inkrementierungsoperation separat ausgeführt werden können, was zu der folgenden Situation führen kann:

Wenn der Wert der Variablen inc zu einem bestimmten Zeitpunkt 10 ist.

Thread 1 führt eine automatische Inkrementierungsoperation für die Variable aus. Thread 1 liest zuerst den ursprünglichen Wert der Variablen inc, und dann wird Thread 1 blockiert.

Thread 2 führt dann eine automatische Inkrementierung durch. Inkrementierungsoperation für die Variable. Thread 2 liest auch den ursprünglichen Wert der Variablen inc. Da Thread 1 nur die Variable inc liest, ohne die Variable zu ändern, wird die Cache-Zeile der Variablen inc im Arbeitsspeicher von Thread 2 nicht ungültig. Daher geht Thread 2 direkt in den Hauptspeicher, um den Wert von inc zu lesen, stellt fest, dass der Wert von inc 10 ist, addiert dann 1, schreibt 11 in den Arbeitsspeicher und schreibt ihn schließlich in den Hauptspeicher.

Dann fügt Thread 1 1 hinzu. Da der Wert von inc gelesen wurde, beachten Sie, dass der Wert von inc im Arbeitsspeicher von Thread 1 zu diesem Zeitpunkt noch 10 beträgt, sodass Thread 1 1 zu inc hinzufügt. Der Wert des letzten Inc ist 11, dann wird 11 in den Arbeitsspeicher und schließlich in den Hauptspeicher geschrieben.

Nachdem die beiden Threads jeweils eine automatische Inkrementierungsoperation durchgeführt haben, erhöht sich inc nur um 1.

Nachdem einige Freunde dies erklärt haben, haben sie möglicherweise Fragen. Nein, ist nicht garantiert, dass die Cache-Zeile ungültig ist, wenn eine Variable in eine flüchtige Variable geändert wird? Dann lesen andere Threads den neuen Wert, ja, das ist richtig. Dies ist die Regel für flüchtige Variablen in der obigen Regel „Vorhergehend“. Es ist jedoch zu beachten, dass der Inc-Wert nicht geändert wird, nachdem Thread 1 die Variable gelesen und blockiert hat. Obwohl volatile sicherstellen kann, dass Thread 2 den Wert der Variablen inc aus dem Speicher liest, ändert Thread 1 ihn nicht, sodass Thread 2 den geänderten Wert überhaupt nicht sieht.

根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。

把上面的代码改成以下任何一种都可以达到效果:

采用synchronized:

public class Test {
    public  int inc = 0;

    public synchronized void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

采用Lock:

public class Test {
    public  int inc = 0;
    Lock lock = new ReentrantLock();

    public  void increase() {
        lock.lock();
        try {
            inc++;
        } finally{
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

采用AtomicInteger:

public class Test {
    public  AtomicInteger inc = new AtomicInteger();

    public  void increase() {
        inc.getAndIncrement();
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

在java 1.5的java.util.concurrent.atomic包下提供了一些原子操作类,即对基本数据类型的 自增(加1操作),自减(减1操作)、以及加法操作(加一个数),减法操作(减一个数)进行了封装,保证这些操作是原子性操作。atomic是利用CAS来实现原子性操作的(Compare And Swap),CAS实际上是利用处理器提供的CMPXCHG指令实现的,而处理器执行CMPXCHG指令是一个原子性操作。

5.3 volatile能保证有序性吗?

volatile能在一定程度上保证有序性。

volatile关键字禁止指令重排序有两层意思:

1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

举个例子:

//x、y为非volatile变量
//flag为volatile变量

x = 2;         //语句1
y = 0;         //语句2
flag = true;   //语句3
x = 4;         //语句4
y = -1;        //语句5

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。

并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。

5.4 volatile的原理和实现机制

这里探讨一下volatile到底如何保证可见性和禁止指令重排序的。

下面这段话摘自《深入理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  2. 它会强制将对缓存的修改操作立即写入主存;

  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

6、使用volatile关键字的场景

synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:

  1. 对变量的写操作不依赖于当前值(比如++操作,上面有例子)

  2. 该变量没有包含在具有其他变量的不变式中

实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

下面列举几个Java中使用volatile的几个场景。

状态标记量

volatile boolean flag = false;

while(!flag){
    doSomething();
}

public void setFlag() {
    flag = true;
}
volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;            

//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

double check

class Singleton{
    private volatile static Singleton instance = null;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

至于为何需要这么写请参考:


Das obige ist der detaillierte Inhalt vonAnalysieren Sie den Beispielcode der Java-Implementierung des flüchtigen Schlüsselworts vom Stammverzeichnis aus (Bild).. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn