Grundlegende Konzepte
1. Sichtbarkeit
Wenn ein Thread eine gemeinsam genutzte Variable ändert, kann ein anderer Thread den geänderten Wert lesen.
2. Speicherbarrieren
Eine Reihe von Anweisungen für den Prozessor, um sequentielle Einschränkungen für Speicheroperationen zu implementieren.
3. Pufferzeile
Die CPU teilt die kleinste Speichereinheit mit, die im Cache zugewiesen werden kann. Wenn der Prozessor die Cachezeile füllt, wird die gesamte Cachezeile geladen.
4. Präfixanweisungen sperren
Präfixanweisungen sperren bewirken auf Mehrkernprozessoren zwei Dinge:
1) Ändern Sie die Daten der Cache-Zeile der aktuellen Prozessorzuordnung zum Systemspeicher.
2) Durch diesen Vorgang des Zurückschreibens in den Speicher werden die von anderen CPUs an der Speicheradresse zwischengespeicherten Daten ungültig.
5. Cache-Kohärenzprotokoll
Bei Multiprozessoren garantiert Null, dass der Cache jedes Prozessors konsistent ist und jeder Prozessor die auf dem Bus weitergegebenen Daten abhört, um zu überprüfen, ob sie zwischengespeichert sind Wert ist abgelaufen. Wenn der Prozessor feststellt, dass die seiner Cache-Zeile entsprechende Adresse geändert wurde, setzt er die Cache-Zeile des aktuellen Prozessors in einen ungültigen Zustand. Wenn der Prozessor diese Daten liest oder schreibt, liest er die Daten erneut aus dem Speicher in den Prozessor-Cache.
6.CAS
CompareAndSwap Vergleichen und tauschen
Die CAS-Operation erfordert die Eingabe von zwei Werten, einem alten Wert (der Wert vor der Durchführung der CAS-Operation, dem erwarteten Wert) und einem neuer Wert: Der aktuelle Wert kann nur dann auf den neuen Wert gesetzt werden, wenn der aktuelle Wert gleich dem alten Wert ist, andernfalls wird er nicht gesetzt. Dies ist eine atomare Operation, die durch Hardware garantiert wird.
7. Neuordnungsregeln
Grundsätzlich gibt es bei JMM nur eine Neuordnungsbeschränkung für Compiler und Prozessoren, solange sich die Ergebnisse der Programmausführung nicht ändern (bezogen auf Single-Threaded oder korrekt synchronisierte Multi). -Thread-Umgebung), dann können Compiler und Prozessor optimiert werden.
Volatil
Wie aus den obigen Lock-Prefix-Anweisungen und dem Cache-Konsistenzprotokoll ersichtlich ist, ist dies das Implementierungsprinzip von Volatile.
Tatsächlich wird beim Schreiben der valatilen Variablen tatsächlich ein Lock-Präfix hinzugefügt, um den Zweck der Sichtbarkeit zu erreichen.
final
Das Final-Feld kann nur einmal explizit zugewiesen werden. Dies bedeutet jedoch nicht, dass das Final-Feld nicht mehrmals initialisiert werden kann.
Zum Beispiel: final int i; bevor i im Konstruktor zugewiesen wird, wird es auf den Standardwert 0 initialisiert. Dies kann durch Debuggen des Codes nachgewiesen werden.
Um sicherzustellen, dass vor der Initialisierung nicht auf den Wert des letzten Felds zugegriffen wird, muss der Programmierer nur eines sicherstellen: Das heißt, im Konstruktor hat das zu erstellende Objekt (dies) nicht „Escape“, dann kann ohne Synchronisationsmittel sichergestellt werden, dass die endgültigen Felder, die von jedem Thread gesehen werden, einschließlich Basistypen und Referenztypen, durch den Konstruktor korrekt initialisiert wurden.
Ein Beispiel für Escapes für ein Objekt, das konstruiert wird:
public class FinalTest{ final int i; static FinalTest obj; public FinalTest(){ i =1; /** *这里会使正在被构造的对象逸出,如果和上一句做了重排序,那么其他线程就可以通过obj访问到还为被初始化的final域。 **/ obj = this; } }
Happens-Before-Regel
Die Bedeutung von Happens-Before
Happens-Before-Regeln werden verwendet, um die sequentielle Beziehung zwischen zwei Vorgängen zu beschreiben. Diese beiden Vorgänge können in einem Thread erfolgen oder nicht. Diese Reihenfolge bedeutet nicht unbedingt die Reihenfolge der Ausführungszeit, sondern vielmehr, dass die Ergebnisse der vorherigen Operation für die nachfolgende Operation sichtbar sind.
Die Happens-Before-Beziehung ist wie folgt definiert:
Wenn eine Operation vor einer anderen Operation geschieht, ist das Ausführungsergebnis der ersten Operation für die zweite Operation sichtbar und das Ergebnis der ersten Operation Die Ausführungsreihenfolge liegt vor der zweiten Operation.
Das Vorhandensein einer Vorher-Vorgang-Beziehung zwischen den beiden Vorgängen bedeutet nicht, dass die spezifische Implementierung der Java-Plattform in der durch die Vorher-Vorgang-Beziehung angegebenen Reihenfolge ausgeführt werden muss. Wenn das Ausführungsergebnis nach der Neuordnung mit dem Ausführungsergebnis gemäß der Havarie-Before-Beziehung übereinstimmt, ist diese Neuordnung nicht illegal.
Wenn beispielsweise A in der Reihenfolge der Programmausführung vor B steht und A eine gemeinsam genutzte Variable ändert und B zufällig die gemeinsam genutzte Variable verwendet, dann muss A vor B erfolgen. Um es einfacher zu sagen: Das bedeutet, dass die Änderung der gemeinsam genutzten Variablen durch A für B sichtbar sein muss, wenn B sie ausführt.
passiert-vor-Regel
Programmsequenzregel: Jede Operation in einem Thread findet statt, bevor jede nachfolgende Operation in diesem Thread ausgeführt wird.
Sperrregeln überwachen: Das Entsperren eines Schlosses erfolgt – bevor das Schloss anschließend gesperrt wird.
Volatile-Regel: Ein Schreibvorgang in ein flüchtiges Feld erfolgt vor jedem nachfolgenden Lesevorgang dieses flüchtigen Felds.
Transitivität: Wenn A vor B passiert und B vor C passiert, dann passiert A vor C.
start()-Regel: Wenn Thread A die Operation ThreadB.start() ausführt, dann erfolgt die ThreadB.start()-Operation von Thread A – vor jeder Operation in Thread B.
join()-Regel: Wenn Thread A die Operation ThreadB.join() ausführt und erfolgreich zurückkehrt, dann wird jede Operation von Thread B ausgeführt, bevor Thread A erfolgreich von der Operation ThreadB.join() zurückkehrt.
Erklärung all dieser Regeln: „Apassiert vor B“ bedeutet nicht, dass A vor B passieren muss, aber es bedeutet, dass, wenn A vor B passiert ist, das Operationsergebnis von A für B sichtbar sein muss