2010-09-01 19 views
9

¿Por qué se considera que el patrón está roto? Me parece bien? ¿Algunas ideas?Patrón de bloqueo comprobado doble: ¿está roto o no?

public static Singleton getInst() { 
    if (instace == null) createInst(); 
    return instace; 
} 

private static synchronized createInst() { 
    if (instace == null) { 
     instace = new Singleton(); 
    } 
} 
+2

Puede evitar este problema por completo utilizando un contenedor DI/IOC y permitiendo que el contenedor controle la vida útil del objeto en lugar de incorporar dicha lógica en el objeto en sí ... No es una respuesta, sino algo en lo que pensar. – Stimul8d

+0

¿El código publicado aquí en la pregunta incluso cuenta como un ejemplo de bloqueo comprobado? La cerradura se revisa una vez. –

+0

ver http://stackoverflow.com/questions/3578604/how-to-solve-the-double-checked-locking-is-broken-declaration-in-java/3578674#3578674 – irreputable

Respuesta

21

Se ve bien a primera vista, pero esta técnica tiene muchos problemas sutiles y generalmente debe evitarse. Por ejemplo, considere la siguiente secuencia de eventos:

  1. avisos Tema A que el valor es no inicializado, por lo que obtiene el bloqueo y comienza a inicializar el valor .
  2. El código generado por el compilador está permitido para actualizar la variable compartida a punto a un objeto construido parcialmente antes de que A ha terminado de realizar la inicialización.
  3. El subproceso B advierte que la variable compartida se ha inicializado (o al menos aparece) y devuelve su valor. Dado que el subproceso B cree que el valor ya se ha inicializado, no se puede adquirir el bloqueo. Si B usa el objeto antes de que la iniciación de realizada por A se vea en B, es probable que el programa falle.

Se puede evitar esto mediante el uso de la palabra clave "volátil" para manejar sus casos únicos correctamente

+2

+1 - ¡La única respuesta que señala el problema del patrón de bloqueo verificado en Java! – helios

+1

Volátil todavía trae algunos problemas, eche un vistazo a mi respuesta. – atamanroman

+1

Ni la carga volátil ni la carga lenta es el enfoque correcto, en su lugar use un inicializador estático –

7

No sé si está roto, sin embargo, no es realmente la solución más eficiente debido a la sincronización, que es bastante costosa. Un mejor enfoque sería utilizar la 'Inicialización On Demand Holder Idiom', que carga su singleton en la memoria la primera vez que se lo exige, como su nombre lo indica, por lo tanto, carga lenta. El mayor beneficio que obtienes con este idioma es que no necesitas sincronizar porque el JLS garantiza que la carga de la clase sea en serie.

entrada de Wikipedia detallada sobre el tema: http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

Otra cosa a tener en cuenta es que, dado que han surgido marcos de inyección de dependencia, tales como la primavera y Guice, instancias de clases se están creando creados y gestionados por estos contenedores y serán le proporcionará un Singleton si lo desea, por lo tanto, no vale la pena romperle la cabeza, a menos que desee aprender la idea detrás del patrón, que es útil. También tenga en cuenta que los singleton proporcionados por estos contenedores IOC son únicos por instancia de contenedor, pero normalmente tendrá un contenedor IOC por aplicación, por lo que no se convertirá en un problema.

+1

Solo se sincroniza la creación de la instancia –

+1

Buena expresión idiomática. (De todos modos la pregunta permanece sin respuesta). Debo agregar que la sincronización es menos costosa con las nuevas JVM. – helios

4

Initialization On Demand Holder Idiom, yup eso es todo:

public final class SingletonBean{ 

    public static SingletonBean getInstance(){ 
     return InstanceHolder.INSTANCE; 
    } 

    private SingletonBean(){} 

    private static final class InstanceHolder{ 
     public static final SingletonBean INSTANCE = new SingletonBean(); 
    } 

} 

Aunque Joshua Bloch también recomienda el patrón Singleton Enumeración de Effective Java Capítulo 2, artículo 3:

// Enum singleton - the prefered approach 
public enum Elvis{ 
    INSTANCE; 
    public void leaveTheBuilding(){ ... } 
} 
6

el problema es la fo llowing: Su JVM puede reordenar su código y los campos no son siempre los mismos para diferentes hilos. Eche un vistazo a esto: http://www.ibm.com/developerworks/java/library/j-dcl.html. El uso de la palabra clave volátil debería solucionar esto, pero está roto antes de Java 1.5.

mayor parte del tiempo de bloqueo único comprobado es más que suficiente rápido, intente esto:

// single checked locking: working implementation, but slower because it syncs all the time 
public static synchronized Singleton getInst() { 
    if (instance == null) 
     instance = new Singleton(); 
    return instance; 
} 

también echar un vistazo a java efectiva, donde se encuentra un gran capítulo sobre este tema.

Para resumir: No haga haga doble bloqueo comprobado, hay mejores idoms.

11

Toda la discusión es una enorme pérdida, sin fin de los tiempos cerebro. 99,9% de las veces, únicos no tiene ningún significativos costos de instalación y no hay razón alguna para configuraciones artificiales para lograr no sincronizada carga garantizada por el vago.

Esta es la forma de escribir un Singleton en Java:

public class Singleton{ 
    private Singleton instance = new Singleton(); 
    private Singleton(){ ... } 
    public Singleton getInstance(){ return instance; } 
} 

Mejor aún, hacen que sea una enumeración:

public enum Singleton{ 
    INSTANCE; 
    private Singleton(){ ... } 
} 
+0

, probablemente también quiera hacer que la clase sea definitiva (aunque un constructor privado va en esa dirección) –

+1

+1, el bloqueo doblemente comprobado es la madre de todas las nanooptimizaciones prematuras – flybywire

+0

Para el constructor enum, privado es redundante. El compilador lo hará privado. Está escrito en la especificación: "En una declaración enum, una declaración del constructor sin modificadores de acceso es privada". Consulte https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.2 – user674669

2

Esto no responde a su pregunta (otros ya lo han hecho), pero Quiero decir que mi experiencia con singletons/objetos inicializados perezosos:

tuvimos un par de hijos únicos en nuestro código. Una vez que tuvimos que añadir un parámetro de constructor para un producto único y tenía un serio problema, porque el constructor de esta Singleton fue invocada en el captador. Hay solamente Estuvieron posibles soluciones:

  • proporcionan un getter estática (u otro singleton) para el objeto que se necesitaba para inicializar este singleton,
  • pasar el objeto para inicializar el singleton como parámetro para el getter o
  • deshacerse del singleton pasando casos alrededor.

Finalmente, la última opción fue el camino a seguir. Ahora inicializamos todos los objetos al inicio de la aplicación y pasamos las instancias requeridas (tal vez como una interfaz pequeña). No hemos lamentado esta decisión, porque

  • las dependencias de una pieza de código es claro como el cristal,
  • podemos probar nuestro código mucho más fácil con las implementaciones de relleno de los objetos requeridos.
+3

El patrón de Singleton es, de hecho, un uso excesivo y, a menudo, un signo de diseño deficiente –

2

La mayoría de las respuestas aquí son correctas acerca de por qué se rompe, pero son incorrectas o sugieren estrategias dudosas para una solución.

Si realmente, realmente debe utilizar un producto único (que en la mayoría de los casos no debe, ya que destruye la capacidad de prueba, combina la lógica sobre cómo construir una clase con el comportamiento de la clase, las camadas de las clases que utilizan el Singleton con el conocimiento de cómo conseguir uno, y conduce a un código más frágil) y están preocupados acerca de la sincronización, la solución correcta es utilizar a static initializer crear una instancia de la instancia.

private static Singleton instance = createInst(); 

public static Singleton getInst() { 
    return instance ; 
} 

private static synchronized createInst() { 
    return new Singleton(); 
} 

La especificación del lenguaje Java garantiza que initiailzers estáticas se llevará a cabo sólo una vez, cuando una clase se carga por primera vez, y de una manera segura para los subprocesos garantizada.

Cuestiones relacionadas