2010-09-03 11 views
14

la instalación de singleton lazy-safe para subprocesos no es fácil de entender para cada codificador, por lo que quería crear una clase en nuestro framework empresarial que hiciera el trabajo.Patrón para creación de instancias de singleton lazy thread-safe en java

¿Qué opina sobre eso? ¿Ves algo malo al respecto? ¿Hay algo similar en Apache Commons? ¿Cómo puedo hacerlo mejor?

Supplier.java

public interface Supplier<T> { 
    public T get(); 
} 

LazyThreadSafeInstantiator.java

public class LazyThreadSafeInstantiator<T> implements Supplier<T> { 
    private final Supplier<T> instanceSupplier; 

    private volatile T obj; 

    public LazyThreadSafeInstantiator(Supplier<T> instanceSupplier) { 
     this.instanceSupplier = instanceSupplier; 
    } 

    @Override 
    // http://en.wikipedia.org/wiki/Double-checked_locking 
    public T get() { 
     T result = obj; // Wikipedia: Note the usage of the local variable result which seems unnecessary. For some versions of the Java VM, it will make the code 25% faster and for others, it won't hurt. 
     if (result == null) { 
      synchronized(this) { 
       result = obj; 
       if (result == null) { 
        result = instanceSupplier.get(); 
        obj = result; 
       } 
      } 
     } 
     return result; 
    } 
} 

Ejemplo de uso:

public class Singleton1 { 
    private static final Supplier<Singleton1> instanceHolder = 
     new LazyThreadSafeInstantiator<Singleton1>(new Supplier<Singleton1>() { 
      @Override 
      public Singleton1 get() { 
       return new Singleton1(); 
      } 
     }); 

    public Singleton1 instance() { 
     return instanceHolder.get(); 
    } 

    private Singleton1() { 
     System.out.println("Singleton1 instantiated"); 
    } 
} 

Gracias

+7

Mi mayor problema es con el patrón en sí. De Wikipedia: Algunos lo consideran un antipatrón, a juzgar por su uso excesivo, introduce limitaciones innecesarias en situaciones en las que no se requiere realmente una instancia única de una clase e introduce estado global en una aplicación. Considere el uso de inyección de dependencia en su lugar. –

+3

Yo recomendaría evitar algo como esto. Use un marco DI en su lugar. –

+0

posible duplicado de [patrón único en Intialización java-lazy] (http://stackoverflow.com/questions/2521895/singleton-pattern-in-java-lazy-intialization) –

Respuesta

54

los perezosos flujos seguros Singleton instantion es un poco no es fácil de entender a cada codificador

No, en realidad es muy, muy fácil:

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

Mejor aún, hacer es una enumeración:

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

Es threadsafe, y es flojo (la inicialización ocurre en el tiempo de carga de la clase, y Java no carga las clases hasta que se mencionan por primera vez).

Hecho es, el 99% del tiempo que no necesita la carga pereza en absoluto. Y del 1% restante, en 0.9% lo anterior es perfectamente perezoso.

¿Ha ejecutado un generador de perfiles y ha determinado que su aplicación se ajusta al 0.01% que realmente necesita lazy-loading-at-first-access? No lo creo Entonces, ¿por qué pierdes tu tiempo inventando estas abominaciones del código Rube Goldbergesque para resolver un problema inexistente?

+0

gracias a todos ustedes por las respuestas. Creo que estoy totalmente equivocado sobre lo que quería lograr. –

+0

Todos me ayudaron a apuntar al problema real, que publiqué en http://stackoverflow.com/questions/3636244/thread-safe-cache-of-one-object-in-java –

+0

En 'clase' muestra' final static' no se encuentra. Lo he corregido Quiero agregar un enlace a esta publicación de otra pregunta. –

3

no es la doble Modelo controlado y el uso de bloqueo volátil broken en compiladores JIT y sistemas multi-core/processor debido al modelo de memoria Java & posibilidad de ejecución fuera de servicio?

En términos más generales, parece que un marco para singletons es excesivo para lo que es esencialmente un patrón bastante sencillo de implementar correctamente.

+1

Tenía la impresión de que se habían arreglado en java 1.5 –

+0

@ mR_fr0g, no. Una parte del problema con este antipatrón es que intenta leer la referencia sin bloqueo.La sincronización correcta requiere bloqueos en * todas * lecturas * y * escrituras para garantizar que los subprocesos vean actualizaciones de otros subprocesos –

+0

Tiene información bastante antigua. Java 1.5 salió a finales de 2004, y esto se ha solucionado desde entonces (siempre que el campo esté marcado como "volátil"). De hecho, Java 1.5 es tan antiguo que Sun dejó de admitirlo en noviembre pasado. –

5

Para una versión que sea más legible (en mi opinión) que la que se presenta en la pregunta, se puede consultar el Initialization on Demand Holder idiom, presentado por Bill Pugh. No solo es seguro para subprocesos teniendo en cuenta el modelo de memoria de Java 5, el singleton también se inicializa de forma lenta.

3

A mi me han sido sobreescritas.

Realmente no veo cómo tener ayudante clase ayuda.

En primer lugar, está utilizando de doble bloqueo idioma, y ​​se ha demostrado una y otra vez roto.

En segundo lugar, si TIENE utilice singleton, por qué no inicializar static final instancia.

public class Singleton1 { 
    private static final Singleton1 instanceHolder = 
     new Singletong1(); 

    public Singleton1 instance() { 
     return instanceHolder; 
    } 

    private Singleton1() { 
     System.out.println("Singleton1 instantiated"); 
    } 
} 

Este código es seguro para subprocesos y se ha demostrado que funciona.

Consulte la respuesta de Vineet Reynolds para cuando necesite inicializar la instancia de singleton en un primer obtenga. En muchos casos, creo que ese enfoque también es excesivo.

0

Estoy de acuerdo con otros carteles y digo que esto parece excesivo, pero he dicho que creo que esto es algo que un desarrollador junior puede equivocarse. Creo que debido a que el comportamiento del proveedor que construye el singleton (que se muestra a continuación) va a ser el mismo en casi todos los casos, me sentiría tentado de poner esto como comportamiento predeterminado en el LazyThreadSafeInstantiator. El uso de la clase interna annonomous cada vez que desee utilizar un singleton es realmente desordenado.

 @Override 
     public Singleton1 get() { 
      return new Singleton1(); 
     } 

Esto se puede hacer proporcionando un constructor sobrecargado que lleva la Clase al singleton requerido.

public class LazyThreadSafeInstantiator<T> implements Supplier<T> { 
    private final Supplier<T> instanceSupplier; 

    private Class<T> toConstruct; 

    private volatile T obj; 

    public LazyThreadSafeInstantiator(Supplier<T> instanceSupplier) { 
     this.instanceSupplier = instanceSupplier; 
    } 

    public LazyThreadSafeInstantiator(Class<t> toConstruct) { 
     this.toConstruct = toConstruct; 
    } 

    @Override 
    // http://en.wikipedia.org/wiki/Double-checked_locking 
    public T get() { 
     T result = obj; // Wikipedia: Note the usage of the local variable result which seems unnecessary. For some versions of the Java VM, it will make the code 25% faster and for others, it won't hurt. 
     if (result == null) { 
      synchronized(this) { 
       result = obj; 
       if (result == null) { 
        if (instanceSupplier == null) { 
         try { 
         Constructor[] c = toConstruct.getDeclaredConstructors(); 
         c[0].setAccessible(true); 
         result = c[0].newInstance(new Object[] {}); 
         } catch (Exception e) { 
         //handle 
         } 
         result = 
        } else { 
         result = instanceSupplier.get(); 
        } 
        obj = result; 
       } 
      } 
     } 
     return result; 
    } 
} 

Esto se usaría como tal.

private static final Supplier<Singleton1> instanceHolder = 
    new LazyThreadSafeInstantiator<Singleton1>(Singleton1.getClass()); 

Esta es mi opinión es un poco más limpio. También podría ampliar esto más para usar argumentos de constructor.

+1

Buena idea, pero tienes que hacer público al constuctor para que funcione. –

+0

Sí Buen punto. Puede usar la reflexión para acceder al constructor y hacer que sea accesible. Sin embargo, sería un poco hacky, así que si lo haces, asegúrate de documentar al máximo. –

0
Lazy<X> lazyX= new Lazy<X>(){ 
    protected X create(){ 
     return new X(); 
    }}; 

X x = lazyX.get(); 

abstract public class Lazy<T> 
{ 
    abstract protected T create(); 

    static class FinalRef<S> 
    { 
     final S value; 
     FinalRef(S value){ this.value =value; } 
    } 

    FinalRef<T> ref = null; 

    public T get() 
    { 
     FinalRef<T> result = ref; 
     if(result==null) 
     { 
      synchronized(this) 
      { 
       if(ref==null) 
        ref = new FinalRef<T>(create()); 
       result = ref; 
      } 
     } 
     return result.value; 
    } 
} 

excepto tal vez la primera get() en un hilo, todas las llamadas() no requieren obtener la sincronización de lectura o volátil. el objetivo original de doble bloqueo comprobado se logra.

+0

exagerado en cuanto a mí. no de la manera más simple. – ses