Pregunta

Java 5 y más solo. Suponga una computadora de memoria compartida multiprocesador (probablemente esté usando una ahora).

Aquí hay un código para la inicialización perezosa de un singleton:

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;
  }
}

Lo hace instance tengo que ser declarado volatile Para evitar que el optimizador reescribiera getInstance () de la siguiente manera (lo que sería correcto en un programa secuencial):

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 reescribe el código, si instance no se declara volatile ¿Todavía se garantiza que se enjuague a la memoria cuando el synchronized el bloque se sale y lee de memoria cuando el synchronized el bloque se ingresa?

EDITAR: Olvidé hacer getInstance () Static. No creo que eso cambie la validez de las respuestas; Todos sabían lo que quise decir.

¿Fue útil?

Solución

Sí, instance debe ser declarado volatile. Incluso entonces, se recomienda no usar el bloqueo de doble verificación. (O para ser preciso, el modelo de memoria 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 un idioma obsoleto y ya no hay necesidad de usarlo; use el lenguaje de inicialización perezoso idioma en cambio.

De Concurrencia de Java en la práctica, Sección 16.2:

El verdadero problema con DCL es la suposición de que lo peor que puede suceder al leer una referencia de objeto compartido sin sincronización es ver erróneamente un valor obsoleto (en este caso, null); En ese caso, el idioma DCL compensa este riesgo intentando nuevamente con el bloqueo retenido. Pero el peor de los casos es realmente considerablemente peor: es posible ver un valor actual de la referencia pero valores obsoletos para el estado del objeto, lo que significa que el objeto podría verse en un estado inválido o incorrecto.

Los cambios posteriores en el JMM (Java 5.0 y posterior) han permitido que DCL funcione si resource está hecho volatile, y el impacto de rendimiento de esto es pequeño ya que volatile Las lecturas suelen ser solo un poco más caras que las lecturas no volátiles. Sin embargo, este es un idioma cuya utilidad ha pasado en gran medida: las fuerzas que lo motivaron (sincronización lenta sin contenido, inicio de JVM lento) ya no están en juego, lo que lo hace menos efectivo como una optimización. El lenguaje de titular de inicialización perezoso ofrece los mismos beneficios y es más fácil de entender.

Otros consejos

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

Wikipedia Y un par de otras preguntas sobre el desbordamiento de la pila tienen buenas discusiones sobre "bloqueo a doble verificación", por lo que aconsejaría leer al respecto. También aconsejaría no usarlo a menos que el perfil muestre una necesidad real de rendimiento en este código en particular.

Hay un idioma más seguro y legible para la inicialización perezosa de Java Singletons:

class Singleton {

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

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

  private Singleton create() {
    ⋮
  }

  private Singleton() { }

}

Si usa el patrón de bloqueo de doble verificación más detallado, debe declarar el campo volatile, como otros ya han notado.

No. No tiene que ser volátil. Ver ¿Cómo resolver la declaración de "bloqueo doble a verificación está roto" en Java?

Los intentos anteriores todos fallaron porque si puedes engañar a Java y evitar volátiles/sincronización, Java puede engañarte y darte una vista incorrecta del objeto. Sin embargo el nuevo final La semántica resuelve el problema. Si obtiene una referencia de objeto (por lectura ordinaria), puede leer de manera segura su final campos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top