java synchronization locking double-checked-locking

java - Doble bloqueo controlado Artículo



synchronization locking (10)

Cubro un montón de esto aquí:

http://tech.puredanger.com/2007/06/15/double-checked-locking/

Estaba leyendo este artículo sobre "bloqueo doblemente comprobado" y, fuera del tema principal del artículo, me preguntaba por qué en algún momento del artículo el autor usa el siguiente idioma:

Listado 7. Intentando resolver el problema de escritura fuera de orden

public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 inst = new Singleton(); //4 } instance = inst; //5 } } } return instance; }

Y mi pregunta es: ¿hay alguna razón para sincronizar dos veces algún código con el mismo bloqueo? ¿Tiene esto algún propósito?

Muchas gracias de antemano.


De acuerdo, pero el artículo decía que

El código en el Listado 7 no funciona debido a la definición actual del modelo de memoria. La especificación de lenguaje Java (JLS) exige que el código dentro de un bloque sincronizado no se mueva fuera de un bloque sincronizado. Sin embargo, no dice que el código que no está en un bloque sincronizado no se puede mover a un bloque sincronizado.

Y también parece que la JVM hace la siguiente traducción a "pseudocódigo" en ASM:

public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 //inst = new Singleton(); //4 instance = new Singleton(); } //instance = inst; //5 } } } return instance; }

Hasta ahora, el punto de no escribe después de que "instancia = inst" no se lleva a cabo?

Leeré ahora el artículo, gracias por el enlace.



El artículo se refiere al modelo de memoria Java anterior a la 5.0 (JMM). Según ese modelo, al salir de un bloque sincronizado, se escribe escritura forzada en la memoria principal. Por lo tanto, parece ser un intento de asegurarse de que el objeto Singleton se expulsa antes de la referencia a él. Sin embargo, no funciona porque la escritura a instancia se puede mover al bloque: el motel Roach.

Sin embargo, el modelo anterior a 5.0 nunca se implementó correctamente. 1.4 debe seguir el modelo 5.0. Las clases se inicializan perezosamente, por lo que también podrías escribir

public static final Singleton instance = new Singleton();

O mejor, no uses singleton porque son malvados.


El objetivo de bloquear dos veces era intentar evitar escrituras fuera de orden. El modelo de memoria especifica dónde pueden ocurrir los cambios de orden, en parte en términos de bloqueos. El bloqueo garantiza que no aparezca ninguna escritura (incluida ninguna dentro del constructor singleton) después de "instancia = inst"; línea.

Sin embargo, para profundizar en el tema, recomendaría el artículo de Bill Pugh . Y nunca lo intentes :)


En cuanto a este modismo, hay un artículo muy recomendable y esclarecedor:

http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1

Por otro lado, creo que dhighwayman.myopenid significa que el escritor ha puesto un bloque sincronizado que hace referencia a la misma clase (sincronizada (Singleton.class)) dentro de otro bloque sincronizado que hace referencia a la misma clase. Puede suceder cuando se crea una nueva instancia (Singleton inst = instance;) dentro de ese bloque y para garantizar que sea seguro para subprocesos, es necesario escribir otro sincronizado.

De lo contrario, no puedo ver ningún sentido.


Jon Skeet tiene razón: lee el artículo de Bill Pugh . La expresión idiomática que utiliza Hans es la forma precisa que no funcionará , y no debería usarse.

Esto no es seguro

private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }

Esto también es inseguro

public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 inst = new Singleton(); //4 } instance = inst; //5 } } } return instance; }

No hagas ninguno de los dos, nunca.

En cambio, sincronice todo el método:

public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }

A menos que esté recuperando este objeto un trillón de veces por segundo, el rendimiento alcanzado, en términos reales, es insignificante.


Consulte el Google Tech Talk sobre el Modelo de memoria de Java para obtener una introducción realmente agradable a los puntos más delicados del JMM. Como falta aquí, también me gustaría señalar el blog de Jeremy Mansons ''Java Concurrency'' esp. la publicación en el bloqueo de Double Checked (cualquiera que esté en el mundo de Java parece tener un artículo sobre esto :).


Para Java 5 y mejor, en realidad hay una variante de doble comprobación que puede ser mejor que sincronizar todo el descriptor de acceso. Esto también se menciona en la Declaración de bloqueo doblemente comprobada :

class Foo { private volatile Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized(this) { if (helper == null) helper = new Helper(); } } return helper; } }

La diferencia clave aquí es el uso de volátiles en la declaración de variables; de lo contrario, no funciona, y de todos modos no funciona en Java 1.4 o menos.


Siguiendo la recomendación de John Skeet :

Sin embargo, para profundizar en el tema, recomendaría el artículo de Bill Pugh. Y nunca lo intentes :)

Y aquí está la clave para el segundo bloque de sincronización:

Este código coloca la construcción del objeto Helper dentro de un bloque interno sincronizado. La idea intuitiva aquí es que debería haber una barrera de memoria en el punto donde se libera la sincronización, y eso debería evitar el reordenamiento de la inicialización del objeto auxiliar y la asignación al ayudante de campo.

Básicamente, con el bloque de sincronización interna, estamos tratando de "engañar" al JMM creando la instancia dentro del bloque de sincronización, para forzar al JMM a ejecutar esa asignación antes de que finalice el bloque de sincronización. Pero el problema aquí es que el JMM nos dirige y está moviendo la asignación que está antes del bloque de sincronización dentro del bloque de sincronización, trasladando nuestro problema al principio.

Esto es lo que entendí de esos artículos, realmente interesante y una vez más gracias por las respuestas.