In welchen Situationen könnte ein leerer synchronisierten Block korrekte Einfädeln Semantik erreichen?
-
22-08-2019 - |
Frage
Ich war auf der Suche durch ein Findbugs Bericht auf meiner Code-Basis und eines des Musters, die ausgelöst wurde, war für ein leerer synchronzied
Block (dh synchronized (var) {}
). Die Dokumentation sagt :
Leere synchronisierten Blöcke sind weit mehr subtil und schwer richtig zu verwenden als die meisten Menschen erkennen und leer synchronisierten Blöcke sind so gut wie nie ein bessere Lösung als weniger gekünstelt Lösungen.
In meinem Fall kam es, weil der Inhalt des Blocks wurde aus hatte kommentiert, aber die synchronized
Aussage war immer noch da. In welchen Situationen könnte ein leerer synchronized
Block korrekte Einfädeln Semantik erreichen?
Lösung
Ein leerer synchronisierten Block wartet, bis niemand sonst diese Synchronizer verwendet. Das mag sein, was Sie wollen, aber weil Sie den nachfolgenden Code in dem synchronisierten Block geschützt haben, ist nichts jemand anderes davon abhalten zu modifizieren, was immer es war man schon lange gewartet haben, während Sie den nachfolgenden Code ausführen. Das ist so gut wie nie, was Sie wollen.
Andere Tipps
Die früheren Antworten nicht das nützlichste, was über leere synchronized
Blöcke zu unterstreichen: sie die Sichtbarkeit von Variablen ändert und andere Aktionen über Threads gewährleisten. Wie jtahlborn zeigt an, erlegt Synchronisation ein „Gedächtnis Barriere“ auf dem Compiler, dass es seine Caches zu leeren zwingt und aufzufrischen. Aber ich habe nicht finden, wo „SNAKE diskutiert“ diese, so schrieb ich eine Antwort selbst.
int variable;
void test() // This code is INCORRECT
{
new Thread( () -> // A
{
variable = 9;
for( ;; )
{
// Do other stuff
}
}).start();
new Thread( () -> // B
{
for( ;; )
{
if( variable == 9 ) System.exit( 0 );
}
}).start();
}
Das obige Programm ist falsch. Der Wert der Variablen könnten lokal in Thread A oder B oder beide zwischengespeichert werden. So B könnte nie den Wert von 9 lesen, die A schreibt, und könnte daher Schleife für immer.
Erstellen Sie eine variable Änderung über Threads sichtbar durch die Verwendung leeren synchronized
Blöcke
Eine mögliche Korrektur ist ein volatile
(effektiv „kein Cache“) Modifikator auf die Variable hinzuzufügen. Manchmal ist dies ineffizient, aber, weil es völlig Caching der Variablen verbietet. Leere synchronized
Blöcke, auf der anderen Seite, verbieten nicht-Caching. Alles, was sie tun, ist den Caches zwingt mit dem Hauptspeicher an bestimmten kritischen Punkten zu synchronisieren. Zum Beispiel: *
int variable;
void test() // Corrected version
{
new Thread( () -> // A
{
variable = 9;
synchronized( o ) {} // Flush to main memory
for( ;; )
{
// Do other stuff
}
}).start();
new Thread( () -> // B
{
for( ;; )
{
synchronized( o ) {} // Refresh from main memory
if( variable == 9 ) System.exit( 0 );
}
}).start();
}
final Object o = new Object();
Wie das Speichermodell garantiert Sichtbarkeit
müssen beide Threads auf demselben Objekt, um synchronisieren Sichtbarkeit zu gewährleisten. Diese Garantie beruht auf dem Java Speichermodell , insbesondere auf der Regel, dass eine „Aktion auf dem Monitor m entsperren synchronisiert-mit alle nachfolgenden Sperren Aktionen auf m“ und damit geschieht zuvor diejenigen Aktionen. So Eine der Unlock des Monitors o am Heck seines synchronized
Block passiert-vor der anschließenden Schlosses B an der Spitze seines Blocks. (Beachten Sie, dann ist es diese seltsame Schwanz-Kopf Ordnung der Beziehung, die erklärt, warum die Körper leer sein.) Da auch, dass A des Schreibvoraus seine Unlock und B das Schloss voran seine Lese, die Beziehung muss verlängert beiden Schreib abzudecken und lesen: < em> Schreib passiert-vor lesen . Es ist dies von entscheidender Bedeutung, erweiterte Beziehung, die das überarbeitete Programm richtig in Bezug auf das Speichermodell macht.
Ich denke, dies ist die wichtigste Anwendung für leere synchronized
Blöcke ist.
* Ich spreche als wäre es eine Frage der Prozessor-Caching weil ich denke, das ist ein hilfreicher Weg, es zu sehen. In Wahrheit, wie Aleksandr Dubinsky hat kommentiert, ‚alle modernen Prozessoren Cache-kohärente. Das geschieht vor-Beziehung ist mehr darüber, was der Compiler erlaubt ist eher zu tun, als die CPU.‘
Früher war es der Fall, dass die Spezifikation impliziert bestimmte Speichersperroperationen aufgetreten. Allerdings hat die Spezifikation jetzt geändert und die ursprüngliche Spezifikation wurde nie richtig umgesetzt. Es kann verwendet werden, für einen anderen Thread zu warten, um die Verriegelung zu lösen, aber die Koordination dass der andere Thread erworben hat bereits die Sperre wäre schwierig.
Synchronisieren tut ein bisschen mehr als nur darauf warten, während unelegant Codierung dies den Effekt erzielen konnte.
http://www.javaperformancetuning.com/news/qotm030.shtml
- Der Faden erhält die Sperre auf dem Monitor für das Objekt diese (unter der Annahme des Monitors entriegelt wird, da sonst die Thread wartet, bis der Monitor entriegelt ist).
- Der Thread-Speicher alle seine Variablen spült, dh er alle seine Variablen effektiv lesen von „main“ Speicher (JVMs schmutzig Sätze verwenden kann dies zu optimieren, so dass nur „schmutziges“ Variablen gespült werden, aber vom Konzept her ist dies die gleiche Abschnitt 17.9 der Java-Sprachspezifikation.) Siehe.
- Der Codeblock ausgeführt (in diesem Fall den Rückgabewert auf den aktuellen Wert von i3 Einstellung, die nur aus „main“ Speicher zurückgesetzt worden sein kann).
- (Alle Änderungen an Variablen normalerweise jetzt zu „main“ Speicher geschrieben werden würde, aber für geti3 () haben wir keine Änderungen.)
- Der Faden hebt die Sperre auf dem Monitor für Objekt dieser.
Für eine tiefer gehende Blick in die Java-Speichermodell, haben einen Blick auf dieses Video von Google ‚Weiterführende Themen in Programmiersprachen‘ Serie: http://www.youtube.com/watch?v=1FX4zco0ziY
Es gibt einen wirklich schönen Überblick darüber, was der Compiler (oft in der Theorie, aber manchmal in der Praxis), um Ihren Code zu tun. Wesentliche Sachen für jeden ernsthaften Java-Programmierer!