Heim  >  Artikel  >  Java  >  Eingehende Analyse des Java-Speichermodells: Neuordnung

Eingehende Analyse des Java-Speichermodells: Neuordnung

黄舟
黄舟Original
2016-12-29 12:01:151341Durchsuche

Datenabhängigkeit

Wenn zwei Operationen auf dieselbe Variable zugreifen und eine der beiden Operationen eine Schreiboperation ist, besteht eine Datenabhängigkeit zwischen den beiden Operationen. Datenabhängigkeiten werden in die folgenden drei Typen unterteilt:
Name
Codebeispiel
Beschreibung
Nach dem Schreiben lesen
a = 1;b = a;
Lesen Sie dies, nachdem Sie eine Variable geschrieben haben Standort .
Nach dem Schreiben schreiben
a = 1;a = 2;
Nachdem Sie eine Variable geschrieben haben, schreiben Sie diese Variable erneut.
Lesen und dann schreiben
a = b;b = 1;
Nachdem Sie eine Variable gelesen haben, schreiben Sie die Variable erneut.
In den oben genannten drei Fällen ändert sich das Ausführungsergebnis des Programms, solange die Ausführungsreihenfolge der beiden Vorgänge neu angeordnet wird.

Wie bereits erwähnt, können Compiler und Prozessoren Vorgänge neu anordnen. Der Compiler und der Prozessor berücksichtigen bei der Neuordnung die Datenabhängigkeiten, und der Compiler und der Prozessor ändern nicht die Ausführungsreihenfolge von zwei Vorgängen mit Datenabhängigkeiten.

Beachten Sie, dass sich die hier genannten Datenabhängigkeiten nur auf die Reihenfolge der in einem einzelnen Prozessor ausgeführten Anweisungen und die in einem einzelnen Thread ausgeführten Operationen beziehen. Datenabhängigkeiten zwischen verschiedenen Prozessoren und Threads werden nicht kompiliert.

Als-ob-seriell-Semantik

Als-ob-seriell-Semantik bedeutet: Egal wie Sie neu anordnen (Compiler und Prozessoren, um die Parallelität zu verbessern), das (Single-Threaded-)Programm The Ausführungsergebnisse können nicht geändert werden. Der Compiler, die Laufzeit und der Prozessor müssen sich alle an die Semantik „Als ob seriell“ halten.

Um der Semantik „Als ob seriell“ zu entsprechen, ordnen der Compiler und der Prozessor Vorgänge mit Datenabhängigkeiten nicht neu an, da eine solche Neuordnung die Ausführungsergebnisse verändert. Wenn jedoch keine Datenabhängigkeiten zwischen Vorgängen bestehen, können diese Vorgänge vom Compiler und Prozessor neu angeordnet werden. Schauen Sie sich zur konkreten Veranschaulichung bitte das folgende Codebeispiel zur Berechnung der Fläche eines Kreises an:

double pi  = 3.14;    //A
double r   = 1.0;     //B
double area = pi * r * r; //C

Die Datenabhängigkeiten der oben genannten drei Operationen sind wie folgt:

Eingehende Analyse des Java-Speichermodells: Neuordnung

Wie in der Abbildung oben gezeigt, besteht eine Datenabhängigkeitsbeziehung zwischen A und C und es gibt auch eine Datenabhängigkeitsbeziehung zwischen B und C. Daher kann C in der schließlich ausgeführten Befehlssequenz nicht vor A und B neu angeordnet werden (C wird vor A und B platziert und das Ergebnis des Programms wird geändert). Es besteht jedoch keine Datenabhängigkeit zwischen A und B, und der Compiler und der Prozessor können die Ausführungsreihenfolge zwischen A und B neu anordnen. Die folgende Abbildung zeigt die beiden Ausführungssequenzen des Programms:


Eingehende Analyse des Java-Speichermodells: Neuordnung

As-if-serial-Semantik schützt Single-Thread-Programme und entspricht den Anforderungen Die „als ob seriell“-Semantik des Compilers, der Laufzeit und des Prozessors wirkt zusammen und erzeugt bei Programmierern, die Single-Thread-Programme schreiben, die Illusion, dass Single-Thread-Programme in Programmreihenfolge ausgeführt werden. Die As-ob-serial-Semantik befreit Single-Threaded-Programmierer von der Sorge, dass eine Neuordnung sie stört, und sie müssen sich keine Sorgen über Probleme mit der Speichersichtbarkeit machen.

Regeln für die Programmsequenz

Gemäß den Regeln für die Programmsequenz „passiert vor“ weist der obige Beispielcode zur Berechnung der Fläche eines Kreises drei Beziehungen „passiert vor“ auf:
A passiert - bevor B;
B passiert – bevor C;
A passiert – vor C

Die dritte passiert-vorher-Beziehung wird hier basierend auf der Transitivität von passiert-vorher abgeleitet.

Hier passiert A – vor B, aber in der tatsächlichen Ausführung kann B vor A ausgeführt werden (siehe die neu geordnete Ausführungsreihenfolge oben). Wie in Kapitel 1 erwähnt, erfordert JMM nicht, dass A vor B ausgeführt werden muss, wenn A vor B geschieht. JMM erfordert lediglich, dass die vorherige Operation (Ergebnis der Ausführung) für die folgende Operation sichtbar ist und dass die vorherige Operation der zweiten Operation in der Reihenfolge vorausgeht. Hier muss das Ausführungsergebnis von Operation A für Operation B nicht sichtbar sein und das Ausführungsergebnis nach der Neuordnung von Operation A und Operation B stimmt mit dem Ergebnis der Ausführung von Operation A und Operation B in der Reihenfolge überein, in der sie vorher ausgeführt wurden. In diesem Fall geht JMM davon aus, dass diese Neuordnung nicht illegal (nicht illegal) ist, und JMM lässt diese Neuordnung zu.

In Computern haben Softwaretechnologie und Hardwaretechnologie ein gemeinsames Ziel: so viel Parallelität wie möglich zu entwickeln, ohne die Ausführungsergebnisse des Programms zu verändern. Compiler und Prozessoren halten sich an dieses Ziel. Aus der Definition von „events-before“ können wir ersehen, dass auch JMM an diesem Ziel festhält.

Die Auswirkung der Neuordnung auf Multithreading

Lassen Sie uns nun sehen, ob die Neuordnung die Ausführungsergebnisse von Multithread-Programmen verändert. Schauen Sie sich bitte den Beispielcode unten an:

class ReorderExample {
int a = 0;
boolean flag = false;

public void writer() {
    a = 1;                   //1
    flag = true;             //2
}

Public void reader() {
    if (flag) {                //3
        int i =  a * a;        //4
        ……
    }
}
}

Die Flag-Variable ist ein Flag, mit dem festgestellt wird, ob Variable a geschrieben wurde. Nehmen Sie hier an, dass es zwei Threads A und B gibt. A führt zuerst die Writer()-Methode aus und dann führt B-Thread die Reader()-Methode aus. Wenn Thread B Operation 4 ausführt, kann er sehen, dass Thread A in Operation 1 in die gemeinsam genutzte Variable a schreibt?

Die Antwort lautet: nicht unbedingt sichtbar.

Da Operation 1 und Operation 2 keine Datenabhängigkeit aufweisen, können der Compiler und der Prozessor diese beiden Operationen auf ähnliche Weise neu anordnen. Operation 3 und Operation 4 haben keine Datenabhängigkeit, sodass der Compiler und der Prozessor diese beiden Operationen neu anordnen können. Schauen wir uns zunächst an, was passieren könnte, wenn Operation 1 und Operation 2 neu angeordnet werden. Bitte schauen Sie sich das Zeitdiagramm für die Programmausführung unten an:

Eingehende Analyse des Java-Speichermodells: Neuordnung

Wie in der Abbildung oben gezeigt, wurden Vorgang 1 und Vorgang 2 neu angeordnet. Wenn das Programm ausgeführt wird, schreibt Thread A zuerst die Flag-Variable flag und dann liest Thread B diese Variable. Da die Bedingung wahr ist, liest Thread B die Variable a. Zu diesem Zeitpunkt wurde die Variable a überhaupt nicht von Thread A geschrieben, und die Semantik des Multithread-Programms hier wird durch Neuordnung zerstört!

※Hinweis: In diesem Artikel werden rote gepunktete Pfeillinien verwendet, um fehlerhafte Lesevorgänge anzuzeigen, und grüne gepunktete Pfeillinien, um korrekte Lesevorgänge anzuzeigen.

Schauen wir uns an, was passiert, wenn die Operationen 3 und 4 neu angeordnet werden (mit Hilfe dieser Neuordnung können wir auch die Steuerungsabhängigkeiten erklären). Das Folgende ist das Ausführungszeitdiagramm des Programms, nachdem die Operationen 3 und 4 neu angeordnet wurden:


Eingehende Analyse des Java-Speichermodells: Neuordnung


Im Programm besteht eine Steuerungsabhängigkeit zwischen Operation 3 und Operation 4. Wenn im Code Steuerabhängigkeiten vorhanden sind, wirkt sich dies auf den Grad der Parallelität bei der Ausführung der Befehlssequenz aus. Zu diesem Zweck nutzen Compiler und Prozessoren die Spekulationsausführung, um die Auswirkungen von Steuerungsabhängigkeiten auf die Parallelität zu überwinden. Am Beispiel der spekulativen Ausführung des Prozessors kann der Prozessor, der Thread B ausführt, a*a im Voraus lesen und berechnen und die Berechnungsergebnisse dann vorübergehend in einem Hardware-Cache namens Neuordnungspuffer ROB speichern. Wenn die Bedingung der nächsten Operation 3 als wahr beurteilt wird, wird das Berechnungsergebnis in die Variable i geschrieben.

Aus der Abbildung können wir ersehen, dass die Rateausführung die Operationen 3 und 4 im Wesentlichen neu anordnet. Eine Neuordnung unterbricht hier die Semantik von Multithread-Programmen!

In einem Single-Thread-Programm ändern Neuordnungsoperationen mit Kontrollabhängigkeiten die Ausführungsergebnisse nicht (aus diesem Grund ermöglicht die As-If-Serial-Semantik Neuordnungsoperationen mit Kontrollabhängigkeiten); Neuordnungsvorgänge mit Steuerungsabhängigkeiten können die Ausführungsergebnisse des Programms verändern.

Das Obige ist eine ausführliche Analyse des Java-Speichermodells: Neuordnung von Inhalten. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!


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