Estoy de acuerdo con uno de los comentarios de John: siempre debe usar un maniquí de bloqueo final al acceder a una variable no final para evitar inconsistencias en caso de que la referencia de la variable cambie. Entonces, en cualquier caso y como primera regla general:
Regla n. ° 1: si un campo no es definitivo, utilice siempre un maniquí de bloqueo final (privado).
Razón # 1: Mantienes el candado y cambias la referencia de la variable tú mismo. Otro hilo que esté esperando fuera del bloqueo sincronizado podrá ingresar al bloque protegido.
Razón # 2: Mantienes el candado y otro hilo cambia la referencia de la variable. El resultado es el mismo: otro hilo puede entrar en el bloque protegido.
Pero cuando se usa un maniquí de bloqueo final, hay otro problema : es posible que obtenga datos incorrectos, porque su objeto no final solo se sincronizará con la RAM cuando llame a sincronizar (objeto). Entonces, como segunda regla empírica:
Regla n. ° 2: Cuando bloquee un objeto no final, siempre debe hacer ambas cosas: Usar un maniquí de bloqueo final y el bloqueo del objeto no final por el bien de la sincronización de RAM. (¡La única alternativa será declarar todos los campos del objeto como volátiles!)
Estos bloqueos también se denominan "bloqueos anidados". Tenga en cuenta que debe llamarlos siempre en el mismo orden, de lo contrario obtendrá un bloqueo :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
}
}
}
}
Como puede ver, escribo los dos candados directamente en la misma línea, porque siempre van juntos. De esta manera, incluso podrías hacer 10 bloqueos de anidación:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
Tenga en cuenta que este código no se romperá si adquiere un bloqueo interno como synchronized (LOCK3)
en otros hilos. Pero se romperá si llama a otro hilo algo como esto:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
Solo hay una solución alternativa para estos bloqueos anidados al manejar campos no finales:
Regla # 2 - Alternativa: Declare todos los campos del objeto como volátiles. (No hablaré aquí sobre las desventajas de hacer esto, por ejemplo, evitar cualquier almacenamiento en cachés de nivel x incluso para lecturas, etc.)
Por lo tanto, aioobe tiene toda la razón: simplemente use java.util.concurrent. O comience a comprender todo sobre la sincronización y hágalo usted mismo con bloqueos anidados. ;)
Para obtener más detalles sobre por qué se rompe la sincronización en campos no finales, eche un vistazo a mi caso de prueba: https://stackoverflow.com/a/21460055/2012947
Y para obtener más detalles por qué necesita sincronizarse debido a la RAM y las cachés, eche un vistazo aquí: https://stackoverflow.com/a/21409975/2012947
o
hace referencia en el momento en que se alcanzó el bloque sincronizado. Si el objeto al que seo
refiere cambia, puede aparecer otro hilo y ejecutar el bloque de código sincronizado.