[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Lazy initialization/Singletons



Acabo de releer sobre Java Memory Model [1] , y sobre el Double-Checked 
Locking [2], y voy a tener que asumir que el mecanismo de inicialización 
tardía (Lazy initalization), usado frecuentemente sobre todo en el patrón 
Singleton, no respeta el modelo. Es válido algorítmicamente, pero no es 
compatible con la especificación del lenguaje.
Para entender los problemas derivados del JMM, hay que centrarse 
fundamentalmente en:
1) Crear una instancia mediante "a = new A();" es en realidad una secuencia de 
acciones:
1.1) reservar memoria en el Heap.
1.2) invocar el constructor.
1.3) asignar a la variable local "a" la dirección de memoria creada.
2) El JMM da libertad a los compiladores y máquinas virtuales a la hora de 
realizar optimizaciones:
2.1) guardar datos en registros o cachés y refrescar el contenido de la 
memoria posteriormente, 
2.2) cambiar el orden en el que se ejecutan las instrucciones, siempre que el 
resultado final sea idéntico al que daría lugar una ejecución secuencial.

Es necesario tener en cuenta ésto para entender los problemas de concurrencia 
que se pueden dar, y que derivan en una cierta incertidumbre sobre lo que en 
realidad dicta el código que escribimos en entornos multi-hilo o 
concurrentes. De hecho, lo que prima es descartar la implementación buscando 
un contraejemplo [3], teniendo en cuenta que si ya es no trivial pensar en 
hebras cooperando para molestarse entre sí, más aún lo es si hay que suponer 
que un "new" seguido de una comparación con "null" puede dar lugar a que la 
lectura se dé antes que la escritura, y que lo que vea la hebra que hace la 
comparación sea un objeto creado, pero sin inicializar. A este respecto, 
conviene resaltar justamente que la palabra reservada "volatile" en Java 
trata precisamente de eso: de impedir que el compilador y/o JVM cambie de 
orden las lecturas y las escrituras del atributo dado.

Resumiendo, parece que hay dos alternativas:
1) Crear una clase aparte y declarar el singleton como una variable estática 
de la misma.
2) Usando instancias ThreadLocal indicando a cada hebra si ha realizado la 
pertinente sincronización o no [3]

Hay un benchmark [4] para ayudarnos a decidir :).

Por simplicidad, me inclino por la primera opción, aunque aprovecho para 
añadir una crítica a XP: ¿Cómo se supone que se puede hacer el código 
autoexplicativo en este caso, de forma que no sea necesario incluir 
comentarios que aclaren su propósito?

Un saludo,
Jose.

[1] http://www.cs.umd.edu/~pugh/java/memoryModel/
[2] http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl
[3] 
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html#ThreadLocal
[4] http://www.cs.umd.edu/~pugh/java/memoryModel/DCL-performance.html