Ich habe etwas bezüglich der Programmreihenfolge und wie es die Umordnung in JMM beeinflusst.
Im Java-Speichermodell ist die Programmreihenfolge ( po ) als die gesamte Reihenfolge der Aktionen in jedem Thread in einem Programm definiert. Laut der JLS , dies induziert hapes-before ( hb ) Kanten:
Wenn
x
undy
Aktionen desselben Threads sind undx
vory
kommt Programmreihenfolge, dann hb (x, y) (d. h.x
passiert-vory
).
Also für ein einfaches Programm P:
%Vor%Ich denke po (1, 2) und po (3, 4) . Also, hb (1, 2) und hb (3, 4) .
Nun nehme ich an, ich möchte einige dieser Aussagen neu ordnen und mir P 'geben:
%Vor%Laut diesem Papier können wir zwei benachbarte Aussagen neu anordnen (zB 1 und 2), vorausgesetzt, die Neuordnung beseitigt keine transitiven Vorkommnisse vor Kanten in einer gültigen Ausführung. Da hb jedoch (teilweise) durch po definiert ist und po eine vollständige Reihenfolge der Aktionen eines Threads ist, scheint es mir so zu sein Es wäre unmöglich, zwei Aussagen neu zu ordnen, ohne hb zu verletzen, also ist P keine legale Umwandlung.
Meine Fragen sind:
Sie vermissen diesen Teil der JLS:
Es sollte angemerkt werden, dass das Vorhandensein einer Vor-Vor-Beziehung zwischen zwei Aktionen nicht notwendigerweise bedeutet, dass sie in dieser Reihenfolge in einer Implementierung stattfinden müssen. Wenn die Neuordnung Ergebnisse liefert, die mit einer legalen Ausführung übereinstimmen, ist dies nicht illegal.
In Ihrem Fall, da 1 und 2 nichts miteinander zu tun haben, können sie gewendet werden. Nun, wenn 2 y = r1
gewesen wäre, dann muss 1 vor 2 für das richtige Ergebnis eintreten.
Das eigentliche Problem tritt bei der Multiprozessor-Ausführung auf. Ohne irgendwelche Vor-Vorher-Grenzen kann T2 beobachten, dass 2 vor 1 auftritt, unabhängig von der Ausführungsreihenfolge.
Dies liegt an CPU-Caching. Nehmen wir an, dass T1 in beliebiger Reihenfolge 1 und 2 ausgeführt wurde. Da keine "happing-before" -Begrenzung vorhanden ist, befinden sich diese Aktionen immer noch im CPU-Cache und abhängig von anderen Anforderungen kann der Teil des Caches mit dem Ergebnis 2 vor dem Teil des Caches geleert werden, der das Ergebnis 1 enthält. p>
Wenn T2 zwischen diesen beiden Cache-Flush-Ereignissen ausgeführt wird, wird beobachtet, dass 2 passiert ist und 1 nicht, d. h. 2 ist vor 1 passiert, so weit T2 weiß.
Wenn dies nicht erlaubt ist, muss T1 eine Vor-Vor-Grenze zwischen 1 und 2 festlegen.
In Java gibt es verschiedene Möglichkeiten, dies zu tun. Der alte Stil wäre, 1 und 2 in separate synchronized
Blöcke zu setzen, weil der Anfang und das Ende eines synchronized
Blocks eine Vor-Vor-Grenze ist, dh jede Aktion vor dem Block vor Aktionen innerhalb des Blocks und jeder Aktion innerhalb des Blocks passiert, bevor Aktionen nach dem Block kommen.
Was Sie als P 'beschrieben haben, ist in Wirklichkeit kein anderes Programm, sondern ein Ausführungs-Trace des gleichen Programms P. Es könnte ein anderes Programm sein, aber dann hätte es andere po und daher anders hb .
Happens-before-Relation schränkt die Neuanordnung von Anweisungen in Bezug auf ihre beobachtbare Wirkung ein, nicht ihre Ausführungsreihenfolge. Action 1 passiert vor 2 , aber sie beobachten nicht das Ergebnis des anderen, so dass sie neu angeordnet werden können. hb garantiert, dass Sie beobachten, dass zwei Aktionen in der richtigen Reihenfolge ausgeführt wurden, aber nur aus dem synchronisierten Kontext (dh von anderen Aktionen, die hb mit 1 bilden) und 2 ). Du kannst an 1 und 2 denken: Lass uns tauschen. Niemand sieht zu! .
Hier ist ein gutes Beispiel von JLS, dass passiert-vor-Idee ganz gut reflektiert:
Beispielsweise muss das Schreiben eines Standardwerts in jedes Feld eines von einem Thread erstellten Objekts nicht vor dem Beginn dieses Threads erfolgen, solange kein Lesevorgang diese Tatsache feststellt.
In der Praxis ist es selten möglich, Schreibvorgänge mit Standardwerten aller Objekte zu erstellen, die vor dem Start von einem Thread erstellt wurden, auch wenn sie mit jeder Aktion in diesem Thread die synchronisierte -Kante bilden. Ein Startthread kann möglicherweise nicht wissen, wie viele Objekte in Laufzeit erstellt werden. Sobald Sie jedoch einen Verweis auf ein Objekt haben, werden Sie feststellen, dass Standardwerte bereits geschrieben wurden. Das Bestellen von Standardschreibvorgängen eines Objekts, das noch nicht konstruiert wurde (oder von dem bekannt ist, dass es konstruiert wurde), kann oft nicht in der Ausführung widergespiegelt werden, es verletzt jedoch nicht die Beziehung happes-before , da es sich um einen beobachtbaren Effekt handelt / p>
Ich denke, ein Schlüsselproblem ist mit Ihrer Konstruktion P'
. Es bedeutet, dass die Art und Weise Nachbestellung funktioniert ist, dass Nachbestellung global ist - das gesamte Programm ist in einzelner Art und Weise neu geordnet (bei jeder Ausführung), die das Speichermodell gehorcht. Dann versuchst du über dieses P'
nachzudenken und findest heraus, dass keine interessanten Umbestellungen möglich sind!
Was tatsächlich geschieht, ist, dass es keine besondere globale Ordnung für Aussagen ist nicht durch ein verwandtes hb Beziehung, so verschiedene Threads können unterschiedliche scheinbare Aufträge auf der gleichen Ausführung sehen. In Ihrem Beispiel gibt es keine Kanten zwischen {1,2} und {3,4} Anweisungen in einem Zustand festgelegt, in der anderen Gruppe in beliebiger Reihenfolge sehen. Zum Beispiel ist es möglich, dass T2
beobachtet 2
vor 1
, aber das dann T3
, was T2
(mit seinen eigenen privaten Variablen) identisch ist, beobachtet das Gegenteil! Also gibt es keinen einzigen Neuordnungs P‘-. Jeder Thread seinen eigenen Umordnungen beobachten können, solange sie mit den JMM Regeln in Einklang stehen
Tags und Links java multithreading concurrency java-memory-model