una - manejo de ventanas java
¿Por qué se usa volátil en este ejemplo de bloqueo doble verificado? (6)
Bueno, no hay un doble bloqueo para el rendimiento. Es un patrón roto .
Dejando las emociones de lado, volatile
está aquí porque sin él cuando el segundo subproceso pasa instance == null
, el primer subproceso podría no construir un new Singleton()
aún: nadie promete que la creación del objeto ocurre, antes de la asignación a la instance
para cualquier subproceso pero el que realmente crea el objeto.
volatile
a su vez, establece la relación de los sucesos anteriores entre lecturas y escrituras, y corrige el patrón roto.
Si está buscando rendimiento, use la clase estática interna del titular.
Desde el libro de patrones de diseño de Head First, el patrón de singleton con doble bloqueo comprobado se ha implementado de la siguiente manera:
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
No entiendo por qué se usa volatile
. ¿El uso volatile
no derrota el propósito de usar doble bloqueo comprobado, es decir, el rendimiento?
Declarar la variable como volatile
garantiza que todos los accesos a ella realmente lean su valor actual de la memoria.
Sin volatile
, el compilador puede optimizar los accesos a la memoria y mantener su valor en un registro, por lo que solo el primer uso de la variable lee la ubicación real de la memoria que contiene la variable. Esto es un problema si la variable es modificada por otro hilo entre el primer y el segundo acceso; el primer subproceso solo tiene una copia del primer valor (pre-modificado), por lo que el segundo enunciado if
prueba una copia obsoleta del valor de la variable.
Según lo citado por @irreputable, volátil no es costoso. Incluso si es costoso, la consistencia debe tener prioridad sobre el rendimiento.
Hay una manera más limpia y elegante para Lazy Singletons.
public final class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
Artículo original: Initialization-on-demand_holder_idiom de wikipedia
En ingeniería de software, el modismo de Inicialización a pedido de titular (patrón de diseño) es un singleton cargado de forma diferida. En todas las versiones de Java, la expresión idiomática permite una inicialización lenta y altamente concurrente y segura con un buen rendimiento
Como la clase no tiene variables static
para inicializar, la inicialización se completa trivialmente.
La definición de clase estática LazyHolder
dentro de ella no se inicializa hasta que la JVM determine que se debe ejecutar LazyHolder.
La clase estática LazyHolder
solo se ejecuta cuando se invoca el método estático getInstance
en la clase Singleton, y la primera vez que esto sucede, la JVM carga e inicializa la clase LazyHolder
.
Esta solución es segura para subprocesos sin requerir construcciones de lenguaje especiales (es decir, volatile
o synchronized
).
Si no lo tenía, un segundo subproceso podría entrar en el bloque sincronizado después de que el primero lo estableciera en nulo, y su caché local aún pensaría que era nulo.
El primero no es correcto (si fuera correcto, sería autodestructivo) sino optimización.
Un buen recurso para entender por qué se necesita volatile
proviene del libro de JCIP . Wikipedia tiene una explicación decente de ese material también.
El problema real es que el Thread A
puede asignar un espacio de memoria, por instance
antes de que termine de construir la instance
. Thread B
verá esa asignación e intentará usarla. Esto produce un Thread B
en el Thread B
porque está utilizando una versión de instance
parcialmente construida.
Una lectura volátil no es realmente cara en sí misma.
Puede diseñar una prueba para llamar a getInstance()
en un ciclo cerrado, para observar el impacto de una lectura volátil; sin embargo, esa prueba no es realista; En tal situación, el programador usualmente llamará a getInstance()
una vez y almacenará en caché la instancia por el tiempo de uso.
Otra impl es mediante el uso de un campo final
(ver wikipedia). Esto requiere una lectura adicional, que puede ser más costosa que la versión volatile
. La versión final
puede ser más rápida en un circuito cerrado, sin embargo, esa prueba es discutible, como se argumentó anteriormente.