Anweisungsneuordnung & passiert-bevor-Beziehung [duplicate]

Lesezeit: 5 Minuten

Benutzer-Avatar
Martin

Im Buch Java Concurrency In Practice wird uns mehrmals gesagt, dass die Anweisungen unseres Programms neu geordnet werden können, entweder vom Compiler, von der JVM zur Laufzeit oder sogar vom Prozessor. Wir sollten also davon ausgehen, dass die Anweisungen des ausgeführten Programms nicht in genau derselben Reihenfolge ausgeführt werden, wie wir sie im Quellcode angegeben haben.

Das letzte Kapitel, in dem das Java-Speichermodell behandelt wird, bietet jedoch eine Auflistung von passiert-vorher Regeln, die angeben, welche Befehlsreihenfolge von der JVM beibehalten wird. Die erste dieser Regeln lautet:

  • “Programmreihenfolgeregel. Jede Aktion in einem Thread findet vor jeder Aktion in diesem Thread statt, die später in der Programmreihenfolge kommt.”

Ich glaube, “Programmreihenfolge” bezieht sich auf den Quellcode.

Meine Frage: Unter der Annahme dieser Regel frage ich mich, welche Anweisung tatsächlich neu geordnet werden kann.

“Aktion” ist wie folgt definiert:

Das Java-Speichermodell wird in Bezug auf Aktionen spezifiziert, die Lese- und Schreibvorgänge in Variablen, Sperren und Entsperren von Monitoren sowie das Starten und Verbinden mit Threads umfassen. Das JMM definiert eine Teilreihenfolge, die aufgerufen wird, passiert vor allen Aktionen innerhalb des Programms. Um zu garantieren, dass der Thread, der Aktion B ausführt, die Ergebnisse von Aktion A sehen kann (unabhängig davon, ob A und B in unterschiedlichen Threads auftreten), muss zwischen A und B eine Vorkommnis-Beziehung vorhanden sein. Wenn keine Vorkommnis-Beziehung zwischen zwei vorhanden ist Operationen, kann die JVM sie nach Belieben neu anordnen.

Andere erwähnte Ordnungsregeln sind:

  • Sperrregel überwachen. Eine Entsperrung einer Monitorsperre erfolgt vor jeder nachfolgenden Sperre derselben Monitorsperre.
  • Regel für flüchtige Variablen. Ein Schreiben in ein flüchtiges Feld erfolgt vor jedem nachfolgenden Lesen desselben Felds.
  • Thread-Start-Regel. Ein Aufruf von Thread.start in einem Thread erfolgt vor jeder Aktion im gestarteten Thread.
  • Thread-Beendigungsregel. Jede Aktion in einem Thread wird ausgeführt, bevor ein anderer Thread erkennt, dass der Thread beendet wurde, entweder durch die erfolgreiche Rückgabe von Thread.join oder durch Thread.isAlive, das false zurückgibt.
  • Unterbrechungsregel. Ein Thread, der einen Interrupt für einen anderen Thread aufruft, geschieht, bevor der unterbrochene Thread den Interrupt erkennt (entweder durch das Auslösen von InterruptedException oder durch den Aufruf von isInterrupted oder interrupted).
  • Finalizer-Regel. Das Ende eines Konstruktors für ein Objekt erfolgt vor dem Start des Finalizers für dieses Objekt.
  • Transitivität. Wenn A vor B passiert und B vor C passiert, dann passiert A vor C.

Benutzer-Avatar
Assyrien

Der Kernpunkt der Programmordnungsregel ist: in einem Faden.

Stellen Sie sich dieses einfache Programm vor (alle Variablen anfangs 0):

T1:

x = 5;
y = 6;

T2:

if (y == 6) System.out.println(x);

Aus Sicht von T1 muss eine Ausführung konsistent sein, indem y nach x zugewiesen wird (Programmreihenfolge). Aus Sicht von T2 muss dies jedoch nicht der Fall sein und T2 könnte 0 ausgeben.

T1 darf tatsächlich zuerst y zuweisen, da die beiden Zuweisungen unabhängig sind und deren Austausch die Ausführung von T1 nicht beeinflusst.

Bei richtiger Synchronisierung druckt T2 immer 5 oder nichts.

BEARBEITEN

Sie scheinen die Bedeutung der Programmreihenfolge falsch zu interpretieren. Die Programmreihenfolgeregel läuft darauf hinaus:

Ob x und y sind Aktionen desselben Threads und x kommt davor y dann in Programmreihenfolge hb(x, y) (dh x passiert-vorher y).

passiert-vorher hat im JMM eine ganz bestimmte Bedeutung. Insbesondere tut es nicht meinen, dass y=6 muss im Anschluss an sein x=5 in T1 aus der Perspektive einer Wanduhr. Es bedeutet lediglich, dass die Abfolge der Aktionen von T1 ausgeführt werden muss im Einklang mit diese Bestellung. Sie können sich auch darauf beziehen JLS 17.4.5:

Zu beachten ist, dass zwischen zwei Aktionen eine Happening-Before-Beziehung vorliegt bedeutet nicht notwendigerweise, dass sie in einer Implementierung in dieser Reihenfolge stattfinden müssen. Ergibt die Nachbestellung Ergebnisse, die mit einer rechtmäßigen Ausführung vereinbar sind, ist sie nicht rechtswidrig.

In dem Beispiel, das ich oben gegeben habe, werden Sie zustimmen, dass aus der Sicht von T1 (dh in einem Single-Thread-Programm) x=5;y=6; ist im Einklang mit y=6;x=5; da Sie die Werte nicht lesen. Eine Anweisung in der nächsten Zeile garantiert in T1, dass diese beiden Aktionen angezeigt werden, unabhängig von der Reihenfolge, in der sie ausgeführt wurden.

  • Danke. Aber es fehlt noch etwas: Wenn die Programmreihenfolgeregel wirklich gilt, sollte y immer nach x in T1 zugewiesen werden, unabhängig von der “Perspektive” (T1 oder T2), oder? Somit sollte T2 immer den passenden x-Wert anzeigen. Das Beispiel, das Sie angeben, ist definitiv wahr, ich diskutiere eher die Genauigkeit der Programmreihenfolgeregel an sich (“Jede Aktion in einem Thread geschieht vor jeder Aktion in diesem Thread, die später in der Programmreihenfolge kommt”).

    – Martin

    21. Mai 2013 um 11:14 Uhr

  • @Martin der Punkt ist das passiert-vorher ist nur eine teilweise Bestellung – während x=5 passiert-vorher y=6 Aufgrund der Programmreihenfolgeregel gibt es keine passiert-vorher überhaupt eine Beziehung zwischen diesen Anweisungen und allem, was in T2 passiert, also ist es OK für T2, das Ergebnis von zu sehen y=6 aber nicht das Ergebnis von x=5. Ob y waren dann volatil, das würde a erzwingen passiert-vorher Beziehung zwischen y=6 in T1 und if(y==6) in T2, in dem Sinne, dass Wenn T2 sieht y=6 dann es muss auch sehen x=5.

    – Ian Roberts

    21. Mai 2013 um 11:56 Uhr


  • Nur um zu booten, Neuordnung von Anweisungen ist, wenn die Ausführungsreihenfolge des Codes von der JVM zur Kompilierungs- oder Laufzeit geändert wird. Dies geschieht, um den Code zu optimieren. Die Befehlsumordnung garantiert die Ausführung der Programmreihenfolge. Dies bedeutet, dass zwei verwandte Anweisungen eine Happens-Before-Beziehung haben, aber zwei nicht verwandte Anweisungen außerhalb der Reihenfolge ausgeführt werden können. Der Kontext, in dem dies zu Problemen führen kann, ist eine Multithread-Umgebung.

    – snr

    4. Juli 2018 um 13:17 Uhr

  • Es gibt keine Programmreihenfolge zwischen Threads, daher sollten keine Annahmen über die Ausführungsreihenfolge nach der Codereihenfolge getroffen werden. Um Neuordnungsprobleme zu vermeiden, müssen Threads ordnungsgemäß synchronisiert werden. Code, der die Ausführung der Codereihenfolge erfordert, sollte in synchronisierten Blöcken platziert werden.

    – snr

    4. Juli 2018 um 13:17 Uhr

1014170cookie-checkAnweisungsneuordnung & passiert-bevor-Beziehung [duplicate]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy