Viele Online -Materialien beschreiben das Java -Speichermodell, mit dem ein Hauptspeicher vorhanden ist, und jeder Arbeiter -Thread hat seinen eigenen Arbeitsspeicher. Im Hauptspeicher und ein Stück im Arbeitsspeicher befinden sich ein Daten. Es gibt verschiedene Atomoperationen zwischen dem Arbeitsspeicher und dem Hauptspeicher, um zu synchronisieren.
Das folgende Bild stammt aus diesem Blog
Aufgrund der kontinuierlichen Entwicklung der Java -Version hat sich das Speichermodell jedoch geändert. Dieser Artikel spricht nur über einige Funktionen des Java -Speichermodells. Unabhängig davon, ob es sich um ein neues Speichermodell oder ein altes Speichermodell handelt, sieht es nach dem Verständnis dieser Funktionen klarer aus.
1. Atomizität
Atomizität bedeutet, dass eine Operation ununterbrochen ist. Selbst wenn mehrere Threads zusammen ausgeführt werden, wird sie nach Beginn einer Operation nicht von anderen Threads gestört.
Es wird allgemein angenommen, dass die Anweisungen der CPU atomare Operationen sind, aber der Code, den wir schreiben, ist nicht unbedingt atomare Operationen.
Zum Beispiel i ++. Diese Operation ist keine atomare Operation, sondern wird im Grunde genommen in 3 Operationen unterteilt, i lesen, +1 durchführen und I. Wert zuweisen.
Angenommen, es gibt zwei Themen. Wenn der erste Thread i = 1 liest, wurde die +1 -Operation noch nicht durchgeführt und zum zweiten Thread umgestellt. Zu diesem Zeitpunkt liest der zweite Thread auch i = 1. Dann führen die beiden Threads nachfolgende +1 Operationen aus und weisen dann Werte zurück, I ist nicht 3, sondern 2. Offensichtlich gibt es Inkonsistenz in den Daten.
Beispielsweise ist das Lesen eines 64-Bit-langen Wertes auf einem 32-Bit-JVM keine Atomoperation. Natürlich liest 32-Bit-JVM 32-Bit-Ganzzahlen als Atomoperation.
2. Bestellung
Während der Parallelität kann die Ausführung des Programms nicht in Ordnung sein.
Wenn ein Computer Code ausführt, wird er nicht unbedingt in der Reihenfolge des Programms ausgeführt.
Klasse orderexample {int a = 0; boolesche Flagge = Falsch; public void writer () {a = 1; Flag = wahr; } public void reader () {if (flag) {int i = a +1; }}} Im obigen Code werden beispielsweise zwei Methoden von zwei Threads aufgerufen. Nach gesundem Menschenverstand sollte der Schreib Thread zuerst a = 1 ausführen und dann Flag = True ausführen. Wenn der Lese -Thread liest, i = 2;
Aber weil a = 1 und flag = true, gibt es keine logische Korrelation. Daher ist es möglich, die Ausführungsreihenfolge umzukehren, und es ist möglich, zuerst Flag = True und dann a = 1 auszuführen. Wenn Flag = true, wechseln Sie zu diesem Zeitpunkt zum Lese -Thread. Zu diesem Zeitpunkt wurde A = 1 noch nicht ausgeführt, dann ist der Lese -Thread i = 1.
Das ist natürlich nicht absolut. Es ist möglich, dass es nicht in Ordnung ist und nicht passieren kann.
Warum ist es nicht in Ordnung? Dies beginnt mit der CPU -Anweisung. Nachdem der Code in Java kompiliert wurde, wird er schließlich in den Montagecode konvertiert.
Die Ausführung einer Anweisung kann in viele Schritte unterteilt werden. Angenommen, die CPU -Anweisung ist in die folgenden Schritte unterteilt
Angenommen, es gibt hier zwei Anweisungen
Im Allgemeinen werden wir der Meinung sind, dass die Anweisungen seriell ausgeführt werden, zuerst Anweisungen 1 ausführen und dann Anweisungen ausführen 2. Angenommen, für jeden Schritt ist 1 CPU -Zeitraum erforderlich, und die Ausführung dieser beiden Anweisungen erfordert 10 CPU -Zeiträume, was zu ineffizient ist, um dies zu tun. Tatsächlich werden Anweisungen parallel ausgeführt. Wenn die erste Anweisung ausgeführt wird, kann die zweite Anweisung natürlich nicht erfolgen, wenn die Anweisungen und dergleichen nicht gleichzeitig besetzt werden können. Wie in der obigen Abbildung gezeigt, werden die beiden Anweisungen parallel auf relativ gestaffelte Weise ausgeführt. Wenn Anweisung 1 die ID ausführt, führt Anweisung 2 die IF aus. Auf diese Weise wurden zwei Anweisungen in nur 6 CPU -Zeiträumen ausgeführt, was relativ effizient war.
Sehen wir uns nach dieser Idee an, wie die A = B+C -Anweisung ausgeführt wird.
Wie in der Abbildung gezeigt, gibt es während der Add -Operation einen Leerlauf (x) Operation, da C, wenn Sie B und C hinzufügen möchten, wenn der X -Betrieb in der Abbildung in der Abbildung nicht aus dem Speicher gelesen wird (C nur aus dem Speicher gelesen wird, wenn der Mem -Vorgang abgeschlossen ist. Es gibt hier eine Frage. Zu diesem Zeitpunkt gibt es keine Schreibback (WB). Es müssen nicht warten, bis die WB ausgeführt wird, bevor das Add ausgeführt wird). Daher gibt es eine Leerlaufzeit (x) in der Add -Operation. Da die EX -Anweisung bei der SW -Operation nicht gleichzeitig mit der EX -Anweisung hinzufügen kann, gibt es eine Leerlaufzeit (x).
Lassen Sie uns als nächstes ein etwas komplizierteres Beispiel geben
a = b+c
D = EF
Die entsprechenden Anweisungen sind wie folgt
Der Grund ist dem oben genannten, daher werde ich es hier nicht analysieren. Wir haben festgestellt, dass es hier viel X gibt und es viele Zeitzyklen verschwendet und auch die Leistung betroffen ist. Gibt es eine Möglichkeit, die Anzahl der XS zu reduzieren?
Wir hoffen, einige Vorgänge zu verwenden, um die Freizeit von X zu füllen, da Add Datenabhängigkeit zu den oben genannten Anweisungen enthält und hoffen, einige Anweisungen ohne Datenabhängigkeit zu verwenden, um die durch Datenabhängigkeit generierte Freizeit zu füllen.
Wir haben die Reihenfolge der Anweisungen geändert
Nachdem die Reihenfolge der Anweisungen geändert hat, wird X beseitigt. Der Gesamtlaufzeitraum ist ebenfalls abgenommen.
Die Neuordnung der Anweisungen kann die Pipeline reibungsloser machen
Natürlich ist das Grundsatz der Unterrichtsumordnung, dass es die Semantik des Serienprogramms nicht zerstören kann. Beispielsweise werden A = 1, B = A+1 solche Anweisungen nicht neu angeordnet, da sich das serielle Ergebnis der Umlagerung von der ursprünglichen unterscheidet.
Die Umlagerung der Anweisungen ist nur eine Möglichkeit, den Compiler oder die CPU zu optimieren, und diese Optimierung hat zu Beginn dieses Kapitels Probleme mit dem Programm verursacht.
Wie löst ich es? Verwenden Sie das volatile Schlüsselwort, diese nachfolgende Serie wird eingeführt.
3. Sichtbarkeit
Die Sichtbarkeit bezieht sich darauf, ob andere Threads die Änderung sofort kennen können, wenn ein Thread den Wert einer gemeinsam genutzten Variablen ändert.
Sichtbarkeitsprobleme können in verschiedenen Links auftreten. Beispielsweise verursacht die gerade erwähnte Neuordnung der Anweisungen auch Sichtbarkeitsprobleme, und außerdem führt die Optimierung des Compilers oder die Optimierung bestimmter Hardware auch Sichtbarkeitsprobleme.
Ein Thread optimiert beispielsweise einen gemeinsam genutzten Wert in den Speicher, während ein anderer Thread den gemeinsam genutzten Wert in den Cache optimiert. Wenn der Wert im Speicher geändert wird, kennt der zwischengespeicherte Wert die Änderung nicht.
Zum Beispiel einige Hardware -Optimierungen, wenn ein Programm mehrmals an dieselbe Adresse schreibt, wird es der Meinung, dass es unnötig ist und nur das letzte Schreiben beibehält, sodass die zuvor geschriebenen Daten in anderen Threads unsichtbar sind.
Kurz gesagt, die meisten Probleme mit der Sichtbarkeit beruht auf der Optimierung.
Schauen wir uns als nächstes ein Sichtbarkeitsproblem an, das sich aus der Ebene der virtuellen Java -Maschine ergibt
Das Problem stammt aus einem Blog
Paket edu.hushi.jvm; /** * * @Author -10 * * */public class VisibilityTest erweitert Thread {private boolean stop; public void run () {int i = 0; während (! Stop) {i ++; } System.out.println ("Finish Loop, i =" + i); } public void stopit () {stop = true; } public boolean getStop () {return stop; } public static void main (String [] args) löst eine Ausnahme aus {vissibilityTest v = new vissibilityTest (); v.Start (); Thread.sleep (1000); v.Stopit (); Thread.Sleep (2000); System.out.println ("Finish Main"); System.out.println (v.getStop ()); }} Der Code ist sehr einfach. Der V -Thread hält i ++ in der while -Schleife, bis der Haupt -Thread die Stoppmethode aufruft und den Wert der Stoppvariablen im V -Thread so ändert, dass die Schleife gestoppt werden.
Probleme treten auf, wenn scheinbar einfacher Code ausgeführt wird. In diesem Programm können Threads im Client-Modus selbst im Client-Modus selbst zugefügt werden. Im Servermodus ist es jedoch zuerst eine unendliche Schleife. (Mehr JVM -Optimierung im Servermodus)
Die meisten der 64-Bit-Systeme sind der Servermodus und werden im Servermodus ausgeführt:
MAIN beenden
WAHR
Nur diese beiden Sätze werden gedruckt, aber die Endschleife wird nicht gedruckt. Sie können jedoch feststellen, dass der Stoppwert bereits wahr ist.
Der Autor dieses Blogs verwendet Tools, um das Programm in Assemblercode wiederherzustellen
Hier wird nur ein Teil des Montagecodes abgefangen, und der rote Teil ist der Schleifenteil. Es ist deutlich zu erkennen, dass nur 0x0193bf9d die Stoppüberprüfung ist, während der rote Teil nicht den Stop -Wert nimmt, sodass eine unendliche Schleife durchgeführt wird.
Dies ist das Ergebnis der JVM -Optimierung. Wie vermeiden Sie es? Verwenden Sie wie das Neubestehen der Richtlinie das volatile Schlüsselwort.
Wenn volatil hinzugefügt wird, stellen Sie es in den Montagecode wieder her und Sie werden feststellen, dass jede Schleife den Stoppwert erhält.
Schauen wir uns als nächstes einige Beispiele in der "Java -Sprachspezifikation" an.
Die obige Abbildung zeigt, dass die Neuordnung der Anweisungen zu unterschiedlichen Ergebnissen führt.
Der Grund, warum R5 = R2 in der obigen Abbildung hergestellt wird, ist, dass r2 = r1.x, r5 = r1.x und es zur Kompilierungszeit direkt auf R5 = R2 optimiert ist. Am Ende sind die Ergebnisse unterschiedlich.
4. Passiert vor
5. Das Konzept der Fadensicherheit
Es bezieht sich auf die Tatsache, dass eine bestimmte Funktions- oder Funktionsbibliothek in einer Umgebung mit mehreren Threaden aufgerufen wird, die lokalen Variablen jedes Threads korrekt verarbeiten und die korrekten Fertigstellung der Programmfunktionen ermöglichen kann.
Zum Beispiel das am Anfang erwähnte i+ ++
Dies wird zu Unsicherheit führen.
Weitere Informationen zur Sicherheit von Threads finden Sie in diesem Blog, den ich zuvor geschrieben habe, oder folgen Sie der nachfolgenden Serie, und Sie werden auch über verwandte Inhalte sprechen.