2010-08-30 6 views
6

Java 5 y superior solamente. Supongamos una computadora de memoria compartida multiprocesador (probablemente esté usando una en este momento).¿La palabra clave Java sincronizada vacía el caché?

Aquí es un código de inicialización perezosa de un producto único:

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

¿Tiene instance tienen que ser declarado volatile con el fin de impedir que el optimizador de reescritura getInstance() de la siguiente manera (lo que sería correcto en un secuencial programa):

public static MySingleton getInstance() { 
    if (instance == null) { 
    synchronized (MySingleton.class) { 
     // instance must be null or we wouldn't be here (WRONG!) 
     instance = new MySingleton(); 
    } 
    } 
} 

Suponiendo que el optimizador no volver a escribir el código, si no se ha declarado instancevolatile está todavía garantizada para ser enviado a la memoria cuando se sale del bloque synchronized y se lee desde la memoria cuando se ingresa el bloque synchronized?

EDITAR: Olvidé hacer que getInstance() static. No creo que eso cambie la validez de las respuestas; todos sabían lo que quería decir.

+0

Sugerencia: puede usar 'javap -c com.example.ClassName' para desensamblar el código, de modo que pueda verificar cómo se ha compilado y si el compilador ha cambiado/optimizado uno u otro. Lo revisé con el de JDK 1.6 y el compilador no eliminó el 'si' anidado. – BalusC

+1

Usar javap es interesante, pero el optimizador en javac suele ser bastante pobre. A propósito. El optimizador real está en el compilador JIT en tiempo de ejecución. – Darron

+0

Pido disculpas por comprobar solo una de las respuestas como "La respuesta". Todos ellos hasta ahora merecen ser revisados, pero solo me permite verificar uno. –

Respuesta

15

Sí, instance debe declararse volatile. Incluso entonces, se aconseja no usar el bloqueo con doble verificación. (O para ser precisos, el modelo de memoria de Java) solía tener un defecto grave que permitía la publicación de objetos parcialmente implementados. Esto se ha solucionado en Java5, todavía DCL es una expresión idiomática obsoleta y ya no es necesario usarla. En su lugar, use lazy initialization holder idiom.

De Java Concurrency in Practice, sección 16.2:

El verdadero problema con DCL es el supuesto de que lo peor que puede suceder cuando se lee una referencia de objeto compartido sin sincronización es ver erróneamente un valor rancio (en este caso , null); en ese caso, la expresión DCL compensa este riesgo al intentar nuevamente con la cerradura retenida. Pero el peor de los casos es considerablemente peor: es posible ver un valor actual de la referencia pero valores obsoletos para el estado del objeto, lo que significa que podría verse que el objeto está en un estado no válido o incorrecto.

Los cambios posteriores en la JMM (Java 5.0 y posterior) han permitido DCL a trabajar si resource se hace volatile, y el impacto en el rendimiento de esto es pequeña, ya que volatile lecturas son por lo general sólo un poco más caro que lee no volátil. Sin embargo, este es un modismo cuya utilidad ha pasado en gran medida: las fuerzas que lo motivaron (sincronización lenta sin control, arranque lento de JVM) ya no están en juego, lo que lo hace menos efectivo como optimización. La expresión idiomática del titular de inicialización perezosa ofrece los mismos beneficios y es más fácil de entender.

+1

+1 Muy interesante. Siempre soy más astuto que stackoverflower. Este es un ejercicio de humildad sin fin. – gawi

+1

Si Edsger Dijkstra todavía estuviese con nosotros, probablemente comentaría que si los programadores expertos son siempre más astutos que stackoverflow, la profesión está en mal estado. –

+0

el idioma de la clase del titular solo funciona para singletons. no es un mecanismo general de inicialización perezosa. – irreputable

1

Sí, ejemplo tiene que ser volátil utilizando una doble comprobación de bloqueo en Java, porque de lo contrario la inicialización de MySingleton podría exponer un objeto construido parcialmente con el resto del sistema. También es cierto que los hilos se sincronizarán cuando lleguen a la declaración "sincronizada", pero eso es demasiado tarde en este caso.

Wikipedia y un par de otras preguntas de desbordamiento de pila tienen buenas discusiones de "bloqueo comprobado doble", por lo que le aconsejo que lea sobre ello. También aconsejo no usarlo a menos que los perfiles muestren una necesidad real de rendimiento en este código en particular.

+0

(del artículo javaworld) Un nonfix sugerido comúnmente es declarar el campo de recursos de SomeClass como volátil. Sin embargo, aunque el JMM evita que las escrituras en variables volátiles se reordenen una con respecto a la otra y garantiza que se descarguen inmediatamente a la memoria principal, aún permite que las lecturas y escrituras de variables volátiles se reordenen con respecto a lecturas y escrituras no volátiles. Eso significa que, a menos que todos los campos de Recursos también sean volátiles, el subproceso B aún puede percibir que el efecto del constructor ocurre después de que el recurso se establece para hacer referencia al Recurso recién creado. – gawi

+1

Eso se corrigió en Java5, aunque vale la pena señalar que en las VM más antiguas, incluso las volátiles no solucionarán el problema. – samkass

1

Hay un lenguaje más seguro y más fácil de leer para la inicialización perezosa de embarazos únicos Java:

class Singleton { 

    private static class Holder { 
    static final Singleton instance = create(); 
    } 

    static Singleton getInstance() { 
    return Holder.instance; 
    } 

    private Singleton create() { 
    ⋮ 
    } 

    private Singleton() { } 

} 

Si se utiliza el patrón de una doble comprobación más exhaustiva de bloqueo, hay que declarar el campo volatile, como otros tienen ya anotado

0

No. No tiene que ser volátil. Ver How to solve the "Double-Checked Locking is Broken" Declaration in Java?

intentos anteriores, todos fallaron porque si puedes engañar a Java y evitar volátil/sincronización, Java puede engañarte y darte una vista incorrecta del objeto. sin embargo, la nueva semántica final resuelve el problema. si obtiene una referencia de objeto (por lectura ordinaria), puede leer con seguridad sus campos final.

+1

Aún así, la expresión de titular de inicialización perezosa logra lo mismo, de una manera más clara, con menos código (por lo tanto, menos posibilidades de errores) y sin necesidad de sincronización explícita. –

+0

que solo funciona para singletons. no para crear perezosamente una cantidad dinámica de objetos en tiempo de ejecución. – irreputable

+1

Esta pregunta se refiere expresamente a los singletons. – erickson

Cuestiones relacionadas