Sincronización y Singleton de Java

117

Aclare mis consultas sobre Singleton y Multithreading:

  • ¿Cuál es la mejor manera de implementar Singleton en Java, en un entorno multiproceso?
  • ¿Qué sucede cuando varios subprocesos intentan acceder al getInstance() método al mismo tiempo?
  • ¿Podemos hacer singleton's getInstance() synchronized?
  • ¿Es realmente necesaria la sincronización cuando se utilizan clases Singleton?
RickDavis
fuente

Respuestas:

211

Si, es necesario. Hay varios métodos que puede utilizar para lograr la seguridad de los subprocesos con la inicialización diferida:

Sincronización draconiana:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

Esta solución requiere que todos los subprocesos estén sincronizados cuando en realidad solo deben estar los primeros.

Verifique la sincronización :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Esta solución asegura que solo los primeros hilos que intentan adquirir su singleton tengan que pasar por el proceso de adquisición del candado.

Inicialización a pedido :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Esta solución aprovecha las garantías del modelo de memoria de Java sobre la inicialización de clases para garantizar la seguridad de los subprocesos. Cada clase solo se puede cargar una vez y solo se cargará cuando sea necesario. Eso significa que la primera vez que getInstancese llama, InstanceHolderse cargará y instancese creará, y dado que esto está controlado por ClassLoaders, no es necesaria ninguna sincronización adicional.

Jeffrey
fuente
23
Advertencia: tenga cuidado con la sincronización comprobada. No funciona correctamente con JVM anteriores a Java 5 debido a "problemas" con el modelo de memoria.
Stephen C
3
-1 Draconian synchronizationy Double check synchronizationgetInstance () - ¡el método debe ser estático!
Grim
2
@PeterRader No es necesario que lo sean static, pero podría tener más sentido si lo fueran. Enmendado según lo solicitado.
Jeffrey
4
No se garantiza que su implementación de bloqueo doble verificado funcione. En realidad, se explica en el artículo que citó para el bloqueo de doble verificación. :) Hay un ejemplo que usa volatile que funciona correctamente para 1.5 y superior (el bloqueo doble verificado simplemente está roto por debajo de 1.5). El soporte de inicialización bajo demanda también citado en el artículo probablemente sería una solución más simple en su respuesta.
pegadoj
2
@MediumOne AFAIK, rno es necesario para la corrección. Es solo una optimización para evitar acceder al campo volátil, ya que eso es mucho más caro que acceder a una variable local.
Jeffrey
69

¡Este patrón realiza una inicialización diferida segura para subprocesos de la instancia sin sincronización explícita!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Funciona porque usa el cargador de clases para hacer toda la sincronización por usted de forma gratuita: MySingleton.Loaderprimero se accede a la clase dentro del getInstance()método, por lo que la Loaderclase se carga cuando getInstance()se llama por primera vez. Además, el cargador de clases garantiza que toda la inicialización estática esté completa antes de que tenga acceso a la clase; eso es lo que le brinda seguridad para los subprocesos.

Es como magia.

En realidad, es muy similar al patrón de enumeración de Jhurtado, pero encuentro que el patrón de enumeración es un abuso del concepto de enumeración (aunque funciona)

Bohemio
fuente
11
La sincronización todavía está presente, solo la aplica la JVM en lugar del programador.
Jeffrey
@Jeffrey Tiene razón, por supuesto, lo estaba escribiendo todo (ver ediciones)
Bohemio
2
Entiendo que no hace ninguna diferencia para la JVM, solo digo que hizo una diferencia para mí en lo que respecta al código auto documentado. Nunca antes había visto mayúsculas en Java sin la palabra clave "final" (o enumeración), obtuve un poco de disonancia cognitiva. Para alguien que programa Java a tiempo completo, probablemente no haría la diferencia, pero si cambia los idiomas de un lado a otro, es útil ser explícito. Lo mismo ocurre con los novatos. Aunque, estoy seguro de que uno se puede adaptar a este estilo bastante rápido; todo en mayúsculas es probablemente suficiente. No me refiero a que me guste tu publicación.
Ruby
1
Excelente respuesta, aunque no obtuve parte de ella. ¿Puede elaborar "Además, el cargador de clases garantiza que toda la inicialización estática esté completa antes de que tenga acceso a la clase? Eso es lo que le da seguridad para los subprocesos". , cómo eso ayuda en la seguridad de los subprocesos, estoy un poco confundido sobre eso.
gaurav jain
2
@ wz366 en realidad, aunque no es necesario, estoy de acuerdo por razones de estilo (ya que es efectivamente final porque ningún otro código puede acceder a él) finaldebería agregarse. Hecho.
Bohemio
21

Si está trabajando en un entorno multiproceso en Java y necesita garantizar que todos esos subprocesos acceden a una sola instancia de una clase, puede usar un Enum. Esto tendrá la ventaja adicional de ayudarlo a manejar la serialización.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

y luego haga que sus hilos usen su instancia como:

Singleton.SINGLE.myMethod();
jhurtado
fuente
8

Sí, necesitas getInstance()sincronizar. Si no es así, podría surgir una situación en la que se puedan crear varias instancias de la clase.

Considere el caso en el que tiene dos subprocesos que llaman getInstance()al mismo tiempo. Ahora imagine que T1 se ejecuta justo después de la instance == nullcomprobación y luego T2 se ejecuta. En este momento, la instancia no se crea o configura, por lo que T2 pasará la verificación y creará la instancia. Ahora imagine que la ejecución vuelve a cambiar a T1. Ahora se crea el singleton, ¡pero T1 ya ha hecho la verificación! ¡Procederá a hacer el objeto nuevamente! Hacer getInstance()sincronizados evita este problema.

Hay algunas formas de hacer que los singleton sean seguros para subprocesos, pero getInstance()sincronizarlos es probablemente la más simple.

Oleksi
fuente
¿Ayudará poniendo el código de creación de objetos en el bloque sincronizado, en lugar de realizar una sincronización completa del método?
RickDavis
@RaoG No. Desea tanto la verificación como la creación en el bloque de sincronización. Necesita que esas dos operaciones sucedan juntas sin interrupciones o la situación que describí anteriormente puede suceder.
Oleksi
7

Enum singleton

La forma más sencilla de implementar un Singleton que sea seguro para subprocesos es usar un Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Este código funciona desde la introducción de Enum en Java 1.5

Bloqueo doble comprobado

Si desea codificar un singleton "clásico" que funcione en un entorno multiproceso (a partir de Java 1.5), debe usar este.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}

Esto no es seguro para subprocesos antes de la 1.5 porque la implementación de la palabra clave volátil era diferente.

Carga temprana de Singleton (funciona incluso antes de Java 1.5)

Esta implementación crea una instancia del singleton cuando se carga la clase y proporciona seguridad para los subprocesos.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}
Dan Moldavan
fuente
2

También puede usar un bloque de código estático para crear una instancia de la instancia en la carga de la clase y evitar los problemas de sincronización de subprocesos.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}
usha
fuente
@Vimsha Un par de cosas más. 1. Debería hacer instancefinal 2. Debería hacer getInstance()estático.
John Vint
¿Qué harías si quieres crear un hilo en el singleton.
Arun George
@ arun-george usa un grupo de subprocesos, un grupo de subprocesos único si es necesario, y rodea con un tiempo (verdadero) -intentar-atrapar-arrojar si desea asegurarse de que su hilo nunca muera, sin importar el error?
tgkprog
0

¿Cuál es la mejor manera de implementar Singleton en Java, en un entorno multiproceso?

Consulte esta publicación para conocer la mejor manera de implementar Singleton.

¿Cuál es una forma eficiente de implementar un patrón singleton en Java?

¿Qué sucede cuando varios subprocesos intentan acceder al método getInstance () al mismo tiempo?

Depende de la forma en que haya implementado el método. Si usa doble bloqueo sin variable volátil, puede obtener un objeto Singleton parcialmente construido.

Consulte esta pregunta para obtener más detalles:

¿Por qué se usa volátil en este ejemplo de bloqueo doble verificado?

¿Podemos sincronizar getInstance () de singleton?

¿Es realmente necesaria la sincronización cuando se utilizan clases Singleton?

No es necesario si implementa el Singleton de las siguientes formas

  1. iniciación estática
  2. enumeración
  3. LazyInitalaization con Initialization-on-demand_holder_idiom

Consulte esta pregunta para obtener más detalles.

Patrón de diseño Java Singleton: Preguntas

Ravindra babu
fuente
0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Fuente: Effective Java -> Item 2

Sugiere usarlo, si está seguro de que la clase siempre permanecerá singleton.

Ashish
fuente