java - modificador - ¿Por qué usar volátiles con bloque sincronizado?
sincronizacion de hilos en java (5)
Esto usa el bloqueo verificado doble, tenga en cuenta que if(uniqueInstance == null)
no está 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 tienes el bloque sincronizado, puedes terminar con 2 hilos llegando a este punto al mismo tiempo.
if(uniqueInstance == null) {
uniqueInstance = new someClass(); <---- here
Y construyes 2 objetos SomeClass, lo que frustra el propósito.
Estrictamente hablando, no necesita volátil, el método podría haber sido
public static someClass getInstance() {
synchronized(FullDictionary.class) {
if(uniqueInstance == null) {
uniqueInstance = new someClass();
}
return uniqueInstance;
}
}
Pero eso implica la sincronización y la serialización de cada hilo que realiza getInstance ().
Vi algunos ejemplos en java donde hacen sincronización en un bloque de código para cambiar alguna variable mientras que esa variable se declaró volátil originalmente ... Vi eso en un ejemplo de clase singleton donde declararon la instancia única como volátil y sincronizaron el bloque que inicializa esa instancia ... Mi pregunta es por qué la declaramos volátil mientras nos sincronizamos, ¿por qué tenemos que hacer ambas cosas? no es uno de ellos es suficiente para el otro?
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 por adelantado.
La sincronización por sí misma sería suficiente en este caso si la primera verificación se encontraba dentro del bloque sincronizado (pero no lo es y un hilo podría no ver los cambios realizados por otro si la variable no fuera volátil). Solo volátil no sería suficiente porque necesita realizar más de una operación atómicamente. ¡Pero cuidado! Lo que tienes aquí es el llamado bloqueo con doble verificación: un idioma común, que desafortunadamente no funciona de manera confiable . Creo que esto ha cambiado desde Java 1.6, pero aún este tipo de código puede ser riesgoso.
EDITAR : 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.
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();
}
}
}
El motivo por el que se comprueba uniqueInstance == null dos veces es reducir la sobrecarga de llamar al 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 se asegura de que todos los subprocesos vean la misma copia, por lo que la primera comprobación fuera del bloque sincronizado verá el valor de la instancia única de una manera que está "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 el segundo cheque lo verá)
Puede hacer la sincronización sin usar el bloque sincronizado. No es necesario usar una variable volátil en él ... las actualizaciones volátiles de la variable de la memoria principal ... y sincronizadas Actualiza todas las variables compartidas a las que se ha accedido desde la memoria principal. Para que pueda usarlas de acuerdo con sus necesidades.
Esta publicación explica la idea detrás de volátil.
También se aborda en el trabajo seminal, Java Concurrency in Practice .
La idea principal es que la concurrencia no solo implica la protección del estado compartido, sino también la visibilidad de ese estado entre subprocesos: aquí es donde entra la volatilidad. (Este contrato más grande está definido por el Modelo de memoria de Java ).