Im folgenden Code:Warum ist 'synchronisiert (neues Objekt()) {} `ein No-Op?
class A {
private int number;
public void a() {
number = 5;
}
public void b() {
while(number == 0) {
// ...
}
}
}
Wenn Verfahren b und dann ein neuer Thread aufgerufen wird gestartet, welches Verfahren ein feuert, dann wird Verfahren b nicht immer sehen die Änderung der number
garantiert und kann somit b
nie enden .
Natürlich könnten wir machen number
volatile
dieses Problem zu beheben. Doch für die akademische Gründen nehmen wir an, dass volatile
ist keine Option:
Die JSR-133 FAQs sagt uns:
Nachdem wir einen synchronisierten Block verlassen, lassen wir den Monitor, der die Wirkung der Spülung des Cache muss Hauptspeicher, so dass von diesem Thread vorgenommene Schreibvorgänge für andere Threads sichtbar sein können. Bevor wir einen synchronisierten Block eingeben können, erfassen wir den Monitor, der den lokalen Prozessor-Cache ungültig macht, so dass Variablen aus dem Hauptspeicher erneut geladen werden. Diese
klingt wie ich beide brauchen nur a
und b
zu betreten und verlassen jede synchronized
-Block überhaupt, egal in welchem Monitor die sie verwenden. Genauer gesagt, es klingt so ...:
class A {
private int number;
public void a() {
number = 5;
synchronized(new Object()) {}
}
public void b() {
while(number == 0) {
// ...
synchronized(new Object()) {}
}
}
}
... das Problem beseitigen würde und wird garantieren, dass b
wird die Änderung a
und damit wird auch schließlich enden sehen.
jedoch die FAQs auch deutlich Zustand:
Eine weitere Folge ist, dass das folgende Muster, das einige Leute Verwendung einer Speicherbarriere zu erzwingen, funktioniert nicht:
synchronized (new Object()) {}
Dies ist eigentlich ein No-Op, und Ihr Compiler kann es vollständig entfernen, , weil der Compiler weiß, dass kein anderer Thread auf den gleichen Monitor synchronisieren wird. Sie müssen eine Vor-Hin-Beziehung für einen Thread einrichten, um die Ergebnisse eines anderen zu sehen.
Jetzt ist das verwirrend. Ich dachte, dass die synchronisierte Anweisung Caches zum Leeren bringen wird. Es kann sicherlich keinen Cache im Hauptspeicher löschen, so dass die Änderungen im Hauptspeicher nur von Threads gesehen werden können, die sich auf demselben Monitor synchronisieren, zumal für volatile, die im Grunde dasselbe tun, wir nicht einmal ein brauchen Monitor, oder irre ich mich dort? Also, warum ist das ein No-Op und verursacht nicht b
durch Garantie zu beenden?
Die Referenz wird nicht gespeichert, daher kann kein anderer Thread auf diese Referenz warten. Was versuchst du zu schützen? –
Diese Frage illustriert perfekt den Grund, warum ich Leute dazu dränge, nicht zu versuchen, die Semantik einer Sprache in Bezug auf implementierungsspezifische Dinge zu erklären oder zu verstehen, die es gibt oder nicht gibt, wie einen fiktiven "lokalen Prozessor-Cache". –
@DavidSchwartz Genau, und eigentlich die Phrase "Cache-Flushing", vor allem, wenn in der Nähe der Phrase "Hauptspeicher" kann sehr irreführend darüber sein, was tatsächlich passiert. Cache-Kohärenz-Protokolle gewährleisten häufig eine Speicherkonsistenz, ohne tatsächlich den langsamen Hauptspeicher zu erreichen. Und wenn Sie JLS und JMM ablehnen und versuchen, über die zugrundeliegende Architektur nachzudenken, müssen Sie Dinge wie die Tatsache berücksichtigen, dass einige Synchronisationsmechanismen die globale Speicherkonsistenz nicht gewährleisten (demonstriert durch IRIW). Also, wie yshavit gesagt hat, ist das Festhalten an der JLS der richtige Weg. –