Heim  >  Artikel  >  Java  >  Detaillierte Einführung in die JVM-Neuordnung in JAVA

Detaillierte Einführung in die JVM-Neuordnung in JAVA

高洛峰
高洛峰Original
2017-01-23 10:20:571375Durchsuche

In gleichzeitigen Programmen achten Programmierer besonders auf die Datensynchronisation zwischen verschiedenen Prozessen oder Threads. Insbesondere wenn mehrere Threads gleichzeitig dieselbe Variable ändern, müssen zuverlässige Synchronisierungs- oder andere Maßnahmen ergriffen werden, um sicherzustellen, dass die Daten korrekt geändert werden Hier gilt ein wichtiger Grundsatz: Machen Sie keine Annahmen über die Reihenfolge, in der Anweisungen ausgeführt werden. Sie können die Reihenfolge, in der Anweisungen zwischen verschiedenen Threads ausgeführt werden, nicht vorhersagen.

Aber in einem Single-Thread-Programm können wir normalerweise leicht davon ausgehen, dass Anweisungen nacheinander ausgeführt werden, sonst können wir uns vorstellen, welche schrecklichen Änderungen am Programm passieren werden. Das ideale Modell ist: Die Reihenfolge, in der verschiedene Anweisungen ausgeführt werden, ist eindeutig und geordnet. Diese Reihenfolge ist die Reihenfolge, in der sie in den Code geschrieben werden, unabhängig vom Prozessor oder anderen Faktoren. Dieses Modell wird als sequentielles Konsistenzmodell bezeichnet Es handelt sich um ein Modell, das auf dem von Neumann-System basiert. Natürlich ist diese Annahme an sich vernünftig und kommt in der Praxis selten ungewöhnlich vor, aber tatsächlich übernimmt keine moderne Multiprozessorarchitektur dieses Modell, weil es einfach zu ineffizient ist. Bei der Kompilierungsoptimierung und der CPU-Pipeline geht es fast immer um die Neuordnung von Befehlen.

Neuordnung zur Kompilierungszeit

Eine typische Neuordnung zur Kompilierzeit besteht darin, die Reihenfolge der Anweisungen anzupassen, um die Anzahl der Registerlesevorgänge und -speicherungen so weit wie möglich zu reduzieren, ohne die Programmsemantik zu ändern Replizieren Sie den gespeicherten Wert des Registers vollständig.

Angenommen, der erste Befehl berechnet einen Wert, weist ihn der Variablen A zu und speichert ihn in einem Register. Der zweite Befehl hat nichts mit A zu tun, sondern muss ein Register belegen (vorausgesetzt, er belegt das Register, in dem sich A befindet). Die dritte Anweisung verwendet den Wert von A und ist unabhängig von der zweiten Anweisung. Wenn dann gemäß dem sequentiellen Konsistenzmodell A in das Register eingetragen wird, nachdem der erste Befehl ausgeführt wurde, existiert A nicht mehr, wenn der zweite Befehl ausgeführt wird, und A wird erneut in das Register eingelesen, wenn der dritte Befehl ausgeführt wird, und währenddessen Bei diesem Vorgang hat sich der Wert von A nicht geändert. Normalerweise vertauscht der Compiler die Positionen des zweiten und dritten Befehls, sodass A am Ende des ersten Befehls im Register vorhanden ist und der Wert von A dann direkt aus dem Register gelesen werden kann, wodurch der Aufwand für wiederholtes Lesen verringert wird.

Die Bedeutung der Neuordnung für Pipelines

Moderne CPUs verwenden fast alle Pipeline-Mechanismen, um die Verarbeitung von Anweisungen zu beschleunigen. Im Allgemeinen erfordert die Verarbeitung eines Befehls mehrere CPU-Taktzyklen und wird ausgeführt Parallel über die Pipeline können mehrere Anweisungen im selben Taktzyklus ausgeführt werden. Die spezifische Methode besteht einfach darin, die Anweisungen in verschiedene Ausführungszyklen wie Lesen, Adressieren, Parsen, Ausführen und andere Schritte zu unterteilen und sie in verschiedenen Komponenten zu platzieren Gleichzeitig sind in der Ausführungseinheit EU die Funktionseinheiten in verschiedene Komponenten unterteilt, z. B. Additionskomponenten, Multiplikationskomponenten, Ladekomponenten, Speicherkomponenten usw., die die parallele Ausführung verschiedener Berechnungen weiter realisieren können.

Die Pipeline-Architektur legt fest, dass Anweisungen parallel ausgeführt werden sollen, nicht wie im sequentiellen Modell vorgesehen. Eine Neuordnung trägt dazu bei, die Pipeline voll auszunutzen und dadurch superskalare Effekte zu erzielen.

Reihenfolge sicherstellen

Obwohl Anweisungen nicht unbedingt in der Reihenfolge ausgeführt werden, in der wir sie geschrieben haben, besteht kein Zweifel daran, dass in einer Single-Threaded-Umgebung der endgültige Effekt der Befehlsausführung derselbe sein sollte wie Der Effekt ist bei sequentieller Ausführung konsistent, andernfalls ist diese Optimierung bedeutungslos.

Normalerweise werden die oben genannten Prinzipien erfüllt, unabhängig davon, ob die Befehlsneuordnung zur Kompilierungszeit oder zur Laufzeit durchgeführt wird.

Neuordnung im Java-Speichermodell

Im Java-Speichermodell (JMM) ist die Neuordnung ein sehr wichtiger Abschnitt, insbesondere bei der gleichzeitigen Programmierung. JMM stellt die semantische Ausführungssemantik durch die Vorher-Vorher-Regel sicher. Wenn Sie möchten, dass der Thread, der Operation B ausführt, die Ergebnisse des Threads, der Vorgang A ausführt, beobachtet, müssen A und B das Vorher-Vorher-Prinzip erfüllen. Andernfalls kann die JVM willkürlich vorgehen Sortieren, um die Programmleistung zu verbessern.

Das Schlüsselwort „volatile“ kann die Sichtbarkeit von Variablen sicherstellen, da sich alle Operationen auf „volatile“ im Hauptspeicher befinden und der Hauptspeicher von allen Threads gemeinsam genutzt wird. Der Preis hierfür ist, dass die Leistung geopfert wird und Register oder Register nicht verwendet werden können Da sie nicht global sind, kann die Sichtbarkeit nicht garantiert werden und es kann zu fehlerhaften Lesevorgängen kommen.

Eine weitere Funktion von volatile besteht darin, eine Neuordnung lokal zu verhindern. Operationsanweisungen für flüchtige Variablen werden nicht neu angeordnet, da bei einer Neuordnung Probleme mit der Sichtbarkeit auftreten können.

Im Hinblick auf die Gewährleistung der Sichtbarkeit können Sperren (einschließlich expliziter Sperren und Objektsperren) sowie das Lesen und Schreiben atomarer Variablen die Sichtbarkeit von Variablen gewährleisten. Die Implementierungsmethoden unterscheiden sich jedoch geringfügig. Beispielsweise stellt die Synchronisierungssperre sicher, dass Daten aus dem Speicher erneut gelesen werden, um den Cache zu aktualisieren. Wenn die Sperre aufgehoben wird, werden die Daten zurück in den Speicher geschrieben dass die Daten sichtbar sind, während flüchtige Variablen einfach den Speicher lesen und schreiben.

Eine ausführlichere Einführung in die JVM-Neuordnung in JAVA und verwandte Artikel finden Sie auf der chinesischen PHP-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