Ich stimme einem Kommentar von John zu: Sie müssen beim Zugriff auf eine nicht endgültige Variable immer einen Dummy für die endgültige Sperre verwenden, um Inkonsistenzen bei Referenzänderungen der Variablen zu vermeiden. Also auf jeden Fall und als erste Faustregel:
Regel 1: Wenn ein Feld nicht endgültig ist, verwenden Sie immer einen (privaten) Dummy für die endgültige Sperre.
Grund Nr. 1: Sie halten die Sperre und ändern die Referenz der Variablen selbst. Ein anderer Thread, der außerhalb der synchronisierten Sperre wartet, kann in den geschützten Block eintreten.
Grund Nr. 2: Sie halten die Sperre und ein anderer Thread ändert die Referenz der Variablen. Das Ergebnis ist das gleiche: Ein anderer Thread kann in den geschützten Block eintreten.
Bei Verwendung eines Dummy für die endgültige Sperre gibt es jedoch ein weiteres Problem : Möglicherweise werden falsche Daten angezeigt, da Ihr nicht endgültiges Objekt nur beim Aufruf von synchronize (Objekt) mit dem RAM synchronisiert wird. Als zweite Faustregel gilt:
Regel 2: Wenn Sie ein nicht endgültiges Objekt sperren, müssen Sie immer beides tun: Verwenden eines endgültigen Sperr-Dummys und der Sperre des nicht endgültigen Objekts für die RAM-Synchronisation. (Die einzige Alternative besteht darin, alle Felder des Objekts als flüchtig zu deklarieren!)
Diese Sperren werden auch als "verschachtelte Sperren" bezeichnet. Beachten Sie, dass Sie sie immer in derselben Reihenfolge aufrufen müssen, da Sie sonst ein Deadlock erhalten :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
//do something with o...
}
}
}
}
Wie Sie sehen, schreibe ich die beiden Schlösser direkt in dieselbe Zeile, weil sie immer zusammen gehören. Auf diese Weise können Sie sogar 10 Verschachtelungssperren erstellen:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
//entering the locked space
}
}
}
}
Beachten Sie, dass dieser Code nicht beschädigt wird, wenn Sie nur eine innere Sperre wie synchronized (LOCK3)
bei einem anderen Thread erwerben . Aber es wird kaputt gehen, wenn Sie einen anderen Thread wie diesen aufrufen:
synchronized (LOCK4) {
synchronized (LOCK1) { //dead lock!
synchronized (LOCK3) {
synchronized (LOCK2) {
//will never enter here...
}
}
}
}
Es gibt nur eine Problemumgehung für solche verschachtelten Sperren beim Behandeln nicht endgültiger Felder:
Regel 2 - Alternative: Deklarieren Sie alle Felder des Objekts als flüchtig. (Ich werde hier nicht über die Nachteile sprechen, z. B. das Verhindern von Speicher in X-Level-Caches, auch für Lesevorgänge.)
Aioobe ist also ganz richtig: Verwenden Sie einfach java.util.concurrent. Oder beginnen Sie, alles über die Synchronisierung zu verstehen, und tun Sie dies selbst mit verschachtelten Sperren. ;)
Weitere Informationen dazu, warum die Synchronisierung bei nicht endgültigen Feldern unterbrochen wird, finden Sie in meinem Testfall: https://stackoverflow.com/a/21460055/2012947
Weitere Informationen dazu, warum Sie aufgrund von RAM und Caches überhaupt synchronisiert werden müssen, finden Sie hier: https://stackoverflow.com/a/21409975/2012947
o
wird, auf das zum Zeitpunkt des Erreichens des synchronisierten Blocks verwiesen wurde. Wenn sich das Objekt, daso
sich auf Änderungen bezieht, ändert, kann ein anderer Thread den synchronisierten Codeblock ausführen.