Ab Seite 291 der OCP Java SE 6 Programmer Practice Exams, Frage 25:
public class Stone implements Runnable {
static int id = 1;
public void run() {
id = 1 - id;
if (id == 0)
pick();
else
release();
}
private static synchronized void pick() {
System.out.print("P ");
System.out.print("Q ");
}
private synchronized void release() {
System.out.print("R ");
System.out.print("S ");
}
public static void main(String[] args) {
Stone st = new Stone();
new Thread(st).start();
new Thread(st).start();
}
}
Eine der Antworten lautet:
Die Ausgabe könnte sein
P Q P Q
Ich habe diese Antwort als richtig markiert. Meine Argumentation:
- Wir starten zwei Threads.
- Zuerst tritt man ein
run()
. - Nach JLS 15.26.1 wird zunächst ausgewertet
1 - id
. Ergebnis ist0
. Es wird auf dem Stapel des Threads gespeichert. Wir sind gerade dabei, das0
statisch zu speichernid
, aber ... - Boom, Scheduler wählt den zweiten Thread aus, der ausgeführt werden soll.
- Also tritt der zweite Thread ein
run()
. Statischid
ist immer noch1
, also führt er die Methode auspick()
.P Q
wird gedruckt. - Der Scheduler wählt den ersten Thread aus, der ausgeführt werden soll. Es nimmt
0
von seinem Stapel und speichert in statischeid
. Der erste Thread wird also auch ausgeführtpick()
und gedrucktP Q
.
In dem Buch steht jedoch geschrieben, dass diese Antwort falsch ist:
Dies ist falsch, da die Zeile
id = 1 - id
den Wertid
zwischen0
und vertauscht1
. Es besteht keine Chance, dass dieselbe Methode zweimal ausgeführt wird.
Ich stimme nicht zu. Ich denke, es gibt eine Chance für das Szenario, das ich oben vorgestellt habe. Ein solcher Tausch ist nicht atomar. Liege ich falsch?
P Q R S
,P R S Q
undP R Q S
, dem ich zustimmen.P R S Q
undP R Q S
auch nicht möglich, dapick
undrelease
synchronisiert sind. Vermisse ich etwas (mein Java ist wahrscheinlich etwas rostig)?release
Methode nicht statisch. AlsoP R S Q
undP R Q S
sind mögliche Lösungen in der Tat. Dies behebt jedoch nicht die Rennbedingung in derrun
Methode, daher ist das Buch in Bezug auf dieses Problem immer noch falsch.Antworten:
Nein, Sie haben absolut Recht - genau wie Ihre Beispielzeitleiste.
Zusätzlich dazu, dass es nicht atomar ist, kann nicht garantiert werden, dass das Schreiben
id
ohnehin vom anderen Thread übernommen wird, da keine Synchronisation stattfindet und das Feld nicht flüchtig ist.Es ist etwas beunruhigend, wenn Referenzmaterial wie dieses falsch ist :(
quelle
id
trotzdem vom anderen Thread übernommen wird ? Dass es irgendwie optimiert werden kann und es kein Schreibenid
durch den zweiten Thread geben kann? Ich verstehe diesen Teil nicht.id
, aber Thread 2 sieht ihn möglicherweise nicht sofort - er sieht möglicherweise den alten Wert.id
im Speicher ändern kann und ein anderer Thread ihn nie sieht, weil er nie weiter als bis zu seinem eigenen Cache schaut.synchronized
oder Mutexen im Allgemeinen "das, was Sie denken, dass es tun sollte", solange Sie sie verwenden, um Ihre Daten mit den Standardmustern zu schützen (wiesynchronized
im Beispiel) über). Es ist nur eine Schande, dass sie es falsch verstanden habenid
.Meiner Meinung nach ist die Antwort in den Übungsprüfungen richtig. In diesem Code führen Sie zwei Threads aus, die Zugriff auf dieselbe statische Variablen-ID haben. Statische Variablen werden auf dem Heap in Java gespeichert, nicht auf dem Stapel. Die Ausführungsreihenfolge von ausführbaren Dateien ist nicht vorhersehbar.
Um jedoch den Wert von id für jeden Thread zu ändern:
1 - id
. Genau genommen werden hier zwei Operationen ausgeführt(-id and +1)
;id
auf dem Heap.Dies bedeutet, dass, obwohl der ID-Wert von jedem der beiden Threads gleichzeitig geändert werden kann, nur der Anfangs- und der Endwert veränderbar sind. Zwischenwerte werden nicht voneinander geändert.
Darüber hinaus kann die Analyse des Codes zeigen, dass id zu jedem Zeitpunkt nur 0 oder 1 sein kann.
Beweis:
Startwert id = 1; Ein Thread ändert es in 0 (
id = 1 - id
). Und der andere Thread bringt es zurück auf 1.Startwert id = 0; Ein Thread ändert es in 1 (
id = 1 - id
). Und der andere Thread bringt es zurück auf 0.Daher ist der Wertezustand von id entweder 0 oder 1 diskret.
Ende des Beweises.
Für diesen Code gibt es zwei Möglichkeiten:
Möglichkeit 1. Thread eins greift zuerst auf die Variablen-ID zu. Dann wird der Wert von id (
id = 1 - id
ändert sich auf 0. Danach wird nur die Methodepick ()
ausgeführt, die gedruckt wirdP Q
. Thread zwei wird die ID zu diesem Zeitpunkt auswertenid = 0
; die Methoderelease()
wird dann ausgeführt, indem R SP Q R S
gedruckt wird. Als Ergebnis wird gedruckt.Möglichkeit 2. Thread zwei greift zuerst auf die Variablen-ID zu. Dann wird der Wert von id (
id = 1 - id
ändert sich auf 0. Danach wird nur die Methodepick ()
ausgeführt, die gedruckt wirdP Q
. Thread eins wertet die ID zu diesem Zeitpunkt ausid = 0
; die Methoderelease()
wird dann ausgeführt, indem R SP Q R S
gedruckt wird. Als Ergebnis wird gedruckt.Es gibt keine anderen Möglichkeiten. Es sollte jedoch beachtet werden, dass Varianten
P Q R S
wieP R Q S
oderR P Q S
usw. aufgrundpick()
einer statischen Methode gedruckt werden können und daher zwischen den beiden Threads geteilt werden. Dies führt zur gleichzeitigen Ausführung dieser Methode, die dazu führen kann, dass die Briefe je nach Plattform in einer anderen Reihenfolge gedruckt werden.In jedem Fall wird die Methode jedoch niemals
pick()
oderrelease ()
zweimal ausgeführt, da sie sich gegenseitig ausschließen . DaherP Q P Q
wird keine Ausgabe sein.quelle
1 - d
(Null) auf dem Stapel des Threads gespeichert. Jetzt kann der Scheduler vor Schritt 3 den laufenden Thread ändern. Der zweite Thread hat also auch Null von1 - d
. Bitte studieren Sie das Szenario, das ich in einer Frage vorgestellt habe, sorgfältig. DeshalbP Q P Q
ist es möglich. Wenn Sie von der bereits auf dieser Seite vorgestellten theoretischen Analyse immer noch nicht überzeugt sind, schauen Sie sich bitte auch den letzten Beitrag hier an , in dem empirisch gezeigt wird, dass dies möglich ist.