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.