37

vi algunos ejemplos en java donde realizan la sincronización en un bloque de código para cambiar alguna variable, mientras que la variable se declaró originalmente volátil .. Vi que en un ejemplo de la clase Singleton, donde declararon la instancia única como volátil y que Sychronized el bloque que inicializa esa instancia ... Mi pregunta es por qué lo declaramos volátil mientras nos sincronizamos, ¿por qué tenemos que hacer ambas cosas? no es uno de ellos es suficiente para el otro?¿por qué usar volátiles con bloque sincronizado?

public class someClass { 
volatile static uniqueInstance = null; 

public static someClass getInstance() { 
     if(uniqueInstance == null) { 
      synchronized(someClass.class) { 
       if(uniqueInstance == null) { 
        uniqueInstance = new someClass(); 
       } 
      } 
     } 
     return uniqueInstance; 
    } 

gracias de antemano.

+2

¿Qué es "volátil static uniqueInstance = null;" ? –

Respuesta

17

sincronización por sí mismo sería suficiente en este caso si la primera verificación estaba dentro de bloque sincronizado (pero no es y un hilo podría no ver los cambios realizados por otro si la variable no eran volátil). Solo volátil no sería suficiente porque necesita realizar más de una operación atómicamente. ¡Pero cuidado! Lo que tenemos aquí es llamado bloqueo doble comprobación - un lenguaje común, que por desgracia does not work reliably. Creo que esto ha cambiado desde Java 1.6, pero aún este tipo de código puede ser riesgoso.

EDIT: cuando la variable es volátil, este código funciona correctamente desde JDK 5 (no 6 como escribí anteriormente), pero no funcionará como se esperaba bajo JDK 1.4 o anterior.

+2

dices que la sincronización sería suficiente si la primera verificación estaba dentro del bloque sincronizado ... pero realizo la misma comprobación nuevamente después de ingresar el bloque de sincronización, por lo que seguramente el siguiente subproceso verá el valor actualizado de la variable. – Dorgham

+0

Cada hilo puede ver una instantánea diferente, por lo que el hecho de que un hilo lea una variable dentro de un bloque sincronizado no significa que otro hilo que lea la variable sin sincronización verá el mismo estado. El estado será coherente entre hilos solo si todos utilizan una sincronización adecuada. Como mencioné, 'volátil' se encarga de eso en su caso desde Java 5 hacia arriba (es decir,' volátil' y 'sincronizado' no solo causan una barrera de memoria, sino que también usan la misma barrera). –

+2

Justo como un aviso a los futuros lectores: el artículo vinculado anteriormente está desactualizado y, como dice la edición, la técnica funciona bien en JDK 5, que se lanzó hace casi 10 años. –

4

This post explica la idea detrás volátil.

También se aborda en el trabajo seminal, Java Concurrency in Practice.

La idea principal es que la concurrencia no sólo implica la protección del estado compartido, sino también la visibilidad de ese estado entre los hilos: Aquí es donde entra volátil en (Este contrato más grande se define por la Java Memory Model.)

6

Este. utiliza el bloqueo comprobado doble, tenga en cuenta que el if(uniqueInstance == null) no se encuentra dentro de la parte sincronizada.

Si uniqueInstance no es volátil, podría "inicializarse" con un objeto parcialmente construido donde partes de él no son visibles a menos que la ejecución de subproceso en el bloque synchronized. volátil hace de esto una operación de todo o nada en este caso.

Si no tiene el bloque sincronizado, puede terminar con 2 hilos llegando a este punto al mismo tiempo.

if(uniqueInstance == null) { 
     uniqueInstance = new someClass(); <---- here 

Y construye 2 objetos SomeClass, lo que infringe el propósito.

En sentido estricto, no es necesario volátiles, el método podría haber sido

public static someClass getInstance() { 
    synchronized(FullDictionary.class) { 
     if(uniqueInstance == null) { 
      uniqueInstance = new someClass(); 
      } 
     return uniqueInstance; 
    } 
} 

Pero que incurre la sincronización y la serialización de cada hilo que realiza getInstance().

+0

Hice la sincronización después de la declaración if para reducir el costo de la sincronización, por lo que mi pregunta es ¿por qué tengo que volverla volátil mientras reviso dos veces después de ingresar al bloque de sincronización? Quiero decir sincronización actualizará todas las variables compartidas de inmediato, por lo que el siguiente subproceso verá el valor actualizado de uniqueInstance – Dorgham

+1

@ user1262445 Como dije, sin volátil un hilo puede ver el objeto como parcialmente construido. Por lo tanto, nunca ingresará al bloque sincronizado, pero devolverá un objeto parcialmente construido si uniqueInstance no es volátil. Es decir. tienes 1 hilo que no ingresa al bloque de sincronización y puede devolver basura ya que otro hilo está justo en el medio del bloque sincrónico. – nos

+0

Mi pregunta es sin 'volititle', de hecho, el primer' if (uniqueInstance == null) {'puede pasar aunque se haya inicializado, pero después de que ingrese al bloque sincronizado, la sentencia de error se solucionará ya que se ha sincronizado entonces 'if (uniqueInstance == null)' será falso y no se creará ninguna instancia nueva, ¿verdad? Entonces, incluso sin valor, el código seguirá funcionando como se espera (solo se creará y se devolverá una misma instancia). Pero de hecho tiene peor rendimiento? ¿Estoy en lo cierto? – Jaskey

0

Puede sincronizar sin utilizar el bloque sincronizado. No es necesario el uso de una variable de volátiles en ella ... actualizaciones volátiles de la una variable de memory..and principal actualización sincronizada todas las variables compartidas que se ha accedido desde la memoria principal .. Por lo tanto se puede usar de acuerdo a su requisito..

-1

Mis dos centavos aquí

Frist una explicación rápida de la intuición de este código

if(uniqueInstance == null) { 
     synchronized(someClass.class) { 
      if(uniqueInstance == null) { 
       uniqueInstance = new someClass(); 
      } 
     } 
    } 

La razón comprueba uniqueInstance == null dos veces es reducir la sobrecarga de llamar el bloque sincronizado que es relativamente más lento. El llamado bloqueo con doble verificación.

En segundo lugar, la razón por la que utiliza sincronización es fácil de entender, hace que las dos operaciones dentro del bloque sincronizado sean atómicas.

Por último, el modificador volátil asegura que todos los hilos vean la misma copia, de modo que la primera comprobación fuera del bloque sincronizado verá el valor de la instancia única de forma "sincronizada" con el bloque sincronizado. Sin el modificador volátil, un subproceso puede asignar un valor a uniqueInstance pero el otro subproceso puede no verlo en la primera comprobación. (Aunque la segunda verificación lo verá)

Cuestiones relacionadas