2012-05-22 15 views
7

Hay algunas preguntas sobre este tema, pero la mayoría se burla de este problema porque no es la intención de la pregunta.¿Debo sincronizar una variable volátil estática?

Si tengo una volátil estática en mi clase:

private static volatile MyObj obj = null; 

y en un método de abajo hago:

public MyObj getMyObj() { 
    if (obj == null) { 
     obj = new MyObj();// costly initialisation 
    } 
    return obj; 
} 

necesitaré para sincronizar que exista un sólo hilo escribe en el campo , o ¿alguna escritura será inmediatamente visible para otros hilos que evalúan el obj == null condicional?

Para decirlo de otra manera: ¿hace volátil tener que sincronizar el acceso a escrituras en una variable estática?

Respuesta

8

Definitivamente necesitará algo de tipo de bloqueo para asegurarse de que solo un hilo escribe en el campo. Independientemente de la volatilidad, dos subprocesos pueden "ver" que obj es nulo, y luego ambos comienzan a inicializarse con su código actual.

Personalmente me gustaría tener una de tres opciones:

  • Inicializar en la carga de clase (a sabiendas de que eso será lento, pero no tan perezoso como esperando hasta getMyObj se llama en primer lugar):

    private static final MyObj obj = new MyObj(); 
    
  • uso de bloqueo incondicional:

    private static MyObj obj; 
    private static final Object objLock = new Object(); 
    
    public static MyObj getMyObj() { 
        synchronized(objLock) { 
         if (obj == null) { 
          obj = new MyObj(); 
         } 
         return obj; 
        } 
    } 
    
  • Uso de una clase anidada para la pereza de esa manera:

    public static MyObj getMyObj() { 
        return MyObjHolder.obj; 
    } 
    
    private static class MyObjHolder { 
        static final MyObj obj = new MyObj(); 
    } 
    
+0

No puedo ir por la ruta del titular: fuerza el singleton en mi clase externa, lo que requiere mucha más reflexión y cambios en el marco general que estoy desarrollando. El problema que necesitaba resolver era la creación atómica de la instancia estática. Usé un método estático y lo declare ('obj') estático final para permitir la asignación atómica y la seguridad concurrente. –

+0

@atc: No, la ruta del titular no fuerza a la clase externa a ser un singleton. No está muy claro lo que quieres decir aquí ... –

+0

Para mayor claridad: esta variable 'volátil estática 'estaba originalmente en una fábrica de objetos que la inyectó en el tiempo de ejecución a las instancias que fue responsable de crear. Esto se debe a los detalles de implementación de dicha var staic ('MyObj' anterior). El uso del constructor 'private' para satisfacer el idioma del titular significaba que no podía mantener las ventajas de la herencia para Object Factory. En lugar de bastardecer el idioma del titular aquí, pensé que iría por la ruta de doble sincronización de verificación ya que tenía la menor fricción con la base de código y se demostró que funcionaba. –

3

Sí, absolutamente debe sincronizar (o utilizar una mejor expresión idiomática como Singleton Holder idiom). De lo contrario, se corre el riesgo de que varios hilos inicialicen su objeto varias veces (y luego utilicen diferentes instancias).

Considérese una secuencia de eventos como éste:

  1. Tema A entra getMyObj() y ve que obj == null
  2. Tema B entra getMyObj() y ve que obj == null
  3. Tema A construye un new MyObj() - llamémoslo objA
  4. El hilo B construye un new MyObj() - llamémoslo objB
  5. Tema Un asigna objA a obj
  6. Tema B asigna objB a obj (que no es null más en este punto, por lo que la referencia a objA, asignado por Tema A, se sobrescribe)
  7. Tema Un sale getMyObj() y se inicia utilizar objA
  8. Tema B sale getMyObj() y comienza a utilizar objB

Este escenario puede suceder con cualquier cantidad de hilos. Tenga en cuenta que aunque aquí, en aras de la simplicidad, asumí un ordenamiento estricto de los eventos, en un entorno de multiproceso real los eventos 1-2, 3-4 y/o 7-8 pueden superponerse parcial o totalmente en el tiempo, sin cambiar el final resultado.

un ejemplo para el lenguaje titular:

public class Something { 
    private Something() { 
    } 

    private static class LazyHolder { 
     public static final Something INSTANCE = new Something(); 
    } 

    public static Something getInstance() { 
     return LazyHolder.INSTANCE; 
    } 
} 

Esto se garantiza que sea seguro, como INSTANCE es final. El modelo de memoria Java garantiza que los campos final se inicializan y se hacen visibles correctamente a cualquier cantidad de subprocesos, al cargar la clase contenedora. Como LazyHolder es private y solo se hace referencia en getInstance(), solo se cargará cuando se llame por primera vez a getInstance(). Y en ese punto, INSTANCE se inicializa en segundo plano y se publica de forma segura por la JVM.

+0

Podría tener el campo estático y usar un inicializador estático o un método estático para hacer la creación, lo que permite sincronizar y probar/capturar la lógica. De esta forma será más legible + No tendré que tener un bloqueo en la llamada 'getMyObj()' porque el inicializador estático se hará antes de que la clase esté disponible. –

+0

@atc, su descripción es ambigua, por lo que no puedo evaluar su seguridad y viabilidad de subprocesos sin una muestra de código. Tenga en cuenta que el idioma del titular tampoco utiliza bloqueos (la JVM tendrá un bloqueo alrededor de la construcción de 'LazyHolder', una vez, pero' getInstance() 'no está sincronizado). –

0

Ese código no es seguro para subprocesos. Si múltiples hilos ejecutan la función, se podrían crear múltiples instancias de MyObj. Necesitas alguna forma de sincronización aquí.

La cuestión fundamental es que este código de bloque:

if (obj == null) { 
    obj = new MyObj();// costly initialisation 
} 

no es atómica. De hecho, está muy lejos de ser atómico.

1

No, todavía tendrá que sincronizar el acceso. volatile permite que los cambios hechos a una variable por un hilo sean vistos por otros hilos.

imaginar el siguiente flujo de ejecución (suponiendo dos hilos T1 & T2):

  1. obj es nulo albergar dudas.
  2. T1: if (obj == null): SI
  3. T2: if (obj == null): SI
  4. T1: crea una nueva instancia de MyObj y asignarla a obj
  5. T2: también crea una nueva instancia de MyObj y la asigna a obj

Crea dos veces un objeto que esperaba que se creara solo una vez. Y este no es el peor escenario. Podría terminar devolviendo un objeto que ya no está asignado a su variable.

0

Sin embargo, otra manera de manejar esto es de doble control (NOTA: sólo funciona con Java 5 o más reciente, véase here para más detalles):

public class DoubleCheckingSingletonExample 
{ 
    private static final Object lockObj = new Object(); 

    private static volatile DoubleCheckingSingletonExample instance; 

    private DoubleCheckingSingletonExample() 
    { 
     //Initialization 
    } 

    public static DoubleCheckingSingletonExample getInstance() 
    { 
     if(instance == null) 
     { 
      synchronized(lockObj) 
      { 
       if(instance == null) 
       { 
        instance = new DoubleCheckingSingletonExample(); 
       } 
      } 
     } 

     return instance; 
    } 
} 

Cuando getInstance se llama a partir de dos hilos simultáneamente, tanto en primer lugar ve la instancia como nula, otra ingresa al bloque sincronizado y crea una instancia del objeto. El otro verá que la instancia ya no es nula y no intentará crear una instancia. Las llamadas posteriores a getInstance verán que la instancia no es nula y no intentarán bloquearla en absoluto.

+0

El bloqueo comprobado doble como este necesita JDK5 para funcionar correctamente: http://cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html –

+2

Lo sentimos, el enlace correcto está aquí: http: //www.cs. umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html –

+0

@DavidHeffernan: gracias por el enlace, no sabía sobre el requisito "JDK5 o más nuevo". Editaré la respuesta para incluir esta información. – esaj

Cuestiones relacionadas