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

812

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

Riyaz Mohammed Ibrahim
fuente
1
"¿Cuál es una manera eficiente de implementar un patrón singleton en Java?" por favor defina eficiente.
Marian Paździoch
medium.com/@kevalpatel2106/… . Este es el artículo completo sobre cómo lograr la seguridad del hilo, la reflexión y la serialización en el patrón singleton. Esta es la buena fuente para comprender los beneficios y las limitaciones de la clase singleton.
Keval Patel el
Como Joshua Bloch señala en Effective Java, enum singleton es el mejor camino a seguir. Aquí he categorizado las diversas implementaciones como perezosas / ansiosas, etc.
isaolmez

Respuestas:

782

Use una enumeración:

public enum Foo {
    INSTANCE;
}

Joshua Bloch explicó este enfoque en su charla Efectiva Java Reloaded en Google I / O 2008: enlace al video . También vea las diapositivas 30-32 de su presentación ( efectiva_java_reloaded.pdf ):

La forma correcta de implementar un Singleton serializable

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Editar: Una parte en línea de "Java efectivo" dice:

"Este enfoque es funcionalmente equivalente al enfoque de campo público, excepto que es más conciso, proporciona la maquinaria de serialización de forma gratuita y proporciona una garantía irrefrenable contra la creación de instancias múltiples, incluso frente a ataques de reflexión o serialización sofisticados. Si bien este enfoque tiene aún por ser ampliamente adoptado, un tipo de enumeración de elemento único es la mejor manera de implementar un singleton ".

Stephen Denne
fuente
203
Creo que la gente debería comenzar a ver las enumeraciones como una clase con una función. Si puede enumerar las instancias de su clase en tiempo de compilación, use una enumeración.
Amir Arad
77
Personalmente, a menudo no encuentro la necesidad de usar el patrón singleton directamente. A veces uso la inyección de dependencia de spring con un contexto de aplicación que contiene lo que se refiere como singletons. Mis clases de utilidad tienden a contener solo métodos estáticos, y no necesito ninguna instancia de ellos.
Stephen Denne el
3
Hola, ¿Alguien puede decirme cómo se puede burlar y probar este tipo de singleton en casos de prueba? Intenté intercambiar una instancia falsa de singleton por este tipo, pero no pude.
Ashish Sharma
29
Supongo que tiene sentido, pero todavía no me gusta. ¿Cómo crearías un singleton que extienda otra clase? Si usa una enumeración, no puede.
chharvey
11
@bvdb: Si quieres mucha flexibilidad, ya has arruinado la implementación de un singleton en primer lugar. La capacidad de crear una instancia independiente cuando la necesita no tiene precio en sí misma.
cHao
233

Dependiendo del uso, hay varias respuestas "correctas".

Desde java5, la mejor manera de hacerlo es usar una enumeración:

public enum Foo {
   INSTANCE;
}

Pre java5, el caso más simple es:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

Repasemos el código. Primero, quieres que la clase sea final. En este caso, he usado la finalpalabra clave para que los usuarios sepan que es definitiva. Luego debe hacer que el constructor sea privado para evitar que los usuarios creen su propio Foo. Lanzar una excepción del constructor evita que los usuarios usen la reflexión para crear un segundo Foo. Luego, crea un private static final Foocampo para contener la única instancia y un public static Foo getInstance()método para devolverlo. La especificación Java asegura que solo se llama al constructor cuando se usa la clase por primera vez.

Cuando tiene un objeto muy grande o un código de construcción pesado Y también tiene otros métodos o campos estáticos accesibles que pueden usarse antes de que se necesite una instancia, entonces y solo entonces debe usar una inicialización diferida.

Puede usar a private static classpara cargar la instancia. El código entonces se vería así:

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

Dado que la línea private static final Foo INSTANCE = new Foo();solo se ejecuta cuando se usa realmente la clase FooLoader, esto se ocupa de la instanciación diferida y se garantiza que sea segura para subprocesos.

Cuando también desea poder serializar su objeto, debe asegurarse de que la deserialización no cree una copia.

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

El método readResolve()se asegurará de que se devuelva la única instancia, incluso cuando el objeto se serializó en una ejecución anterior de su programa.

Roel Spilker
fuente
32
La comprobación de la reflexión es inútil. Si otro código está utilizando la reflexión en privado, es Game Over. No hay razón para siquiera tratar de funcionar correctamente bajo tal mal uso. Y si lo intenta, de todos modos será una "protección" incompleta, solo un montón de código desperdiciado.
Wouter Coekaerts
55
> "Primero, quieres que la clase sea final". ¿Podría alguien dar más detalles sobre esto, por favor?
PlagueHammer
2
La protección de deserialización está completamente rota (creo que esto se menciona en Effective Java 2nd Ed).
Tom Hawtin - tackline el
99
-1 este no es el caso más simple, es artificial e innecesariamente complejo. Mire la respuesta de Jonathan para la solución más simple que es suficiente en el 99.9% de todos los casos.
Michael Borgwardt
2
Esto es útil cuando su singleton necesita heredar de una superclase. No puede usar el patrón enton singleton en este caso, ya que las enumeraciones no pueden tener una superclase (sin embargo, pueden implementar interfaces). Por ejemplo, Google Guava usa un campo final estático cuando el patrón enton
Etienne Neveu
139

Descargo de responsabilidad: acabo de resumir todas las respuestas increíbles y lo escribí en mis palabras.


Mientras implementamos Singleton, tenemos 2 opciones
1. Carga diferida
2. Carga temprana

La carga diferida agrega un poco de sobrecarga (para ser sincero), así que úselo solo cuando tenga un objeto muy grande o un código de construcción pesado Y también tenga otros métodos o campos estáticos accesibles que puedan usarse antes de que se necesite una instancia, entonces y solo entonces necesita usar una inicialización diferida. De lo contrario, elegir la carga temprana es una buena opción.

La forma más sencilla de implementar Singleton es

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }
}

Todo está bien, excepto su singleton cargado temprano. Probemos singleton cargado perezoso

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

Hasta ahora todo bien, pero nuestro héroe no sobrevivirá mientras lucha solo con múltiples hilos malvados que quieren muchas instancias de nuestro héroe. Así que vamos a protegerlo del mal multihilo

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

pero no es suficiente para proteger al héroe, ¡¡¡En serio !!! Esto es lo mejor que podemos / debemos hacer para ayudar a nuestro héroe

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

Esto se llama "modismo de bloqueo de doble verificación". Es fácil olvidar la declaración volátil y difícil de entender por qué es necesario.
Para más detalles: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Ahora estamos seguros sobre el hilo del mal, pero ¿qué pasa con la cruel serialización? Tenemos que asegurarnos de que incluso durante la deserialización no se cree ningún objeto nuevo

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

El método readResolve() se asegurará de que se devuelva la única instancia, incluso cuando el objeto se serializó en una ejecución anterior de nuestro programa.

Finalmente, hemos agregado suficiente protección contra hilos y serialización, pero nuestro código se ve voluminoso y feo. Vamos a darle un cambio a nuestro héroe

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Sí, este es nuestro mismo héroe :)
Desde la líneaprivate static final Foo INSTANCE = new Foo(); solo se ejecuta cuando la clase FooLoaderse usa realmente, esto se encarga de la instanciación perezosa,

y se garantiza que sea seguro para subprocesos.

Y hemos llegado tan lejos, aquí está la mejor manera de lograr todo lo que hicimos, es la mejor manera posible

 public enum Foo {
       INSTANCE;
   }

Que internamente será tratado como

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

¡Eso es! No más miedo a la serialización, hilos y código feo. También ENUMS singleton están perezosamente inicializados .

Este enfoque es funcionalmente equivalente al enfoque de campo público, excepto que es más conciso, proporciona la maquinaria de serialización de forma gratuita y ofrece una garantía irrefrenable contra la creación de instancias múltiples, incluso ante ataques de reflexión o serialización sofisticados. Si bien este enfoque aún no se ha adoptado ampliamente, un tipo de enumeración de un solo elemento es la mejor manera de implementar un singleton.

-Joshua Bloch en "Java efectivo"

Ahora te habrás dado cuenta de por qué las ENUMS se consideran la mejor manera de implementar Singleton y gracias por tu paciencia :) Lo
actualicé en mi blog .

xyz
fuente
3
Solo una aclaración: los singletons implementados usando enum se inicializan perezosamente. Detalles aquí: stackoverflow.com/questions/16771373/…
2
gran respuesta. Una última cosa, anular el método de clonación para lanzar una excepción.
riship89
2
@xyz buenas explicaciones, realmente disfruté y aprendí muy fácilmente y espero nunca olvidar esto
Premraj
1
Una de las mejores respuestas que he tenido en rojo en stackoverflow. ¡Gracias!
Shay Tsadok
1
No es un problema con el uso de serialización enumeraciones como un producto único: Cualquier miembro de los valores de campo son no serializados y por lo tanto no se restauran. Consulte la Especificación de serialización de objetos Java, versión 6.0 . Otro problema: No versionado - todos los tipos de enumeración se fija serialVersionUIDde 0L. Tercer problema: Sin personalización: Cualquier método writeObject, readObject, readObjectNoData, writeReplace y readResolve específico de la clase definido por los tipos de enumeración se ignora durante la serialización y la deserialización.
Basil Bourque
124

La solución publicada por Stu Thompson es válida en Java5.0 y posterior. Pero preferiría no usarlo porque creo que es propenso a errores.

Es fácil olvidar la declaración volátil y difícil de entender por qué es necesario. Sin el volátil, este código ya no sería seguro para subprocesos debido al antipatrón de bloqueo doblemente verificado. Vea más sobre esto en el párrafo 16.2.4 de la concurrencia de Java en la práctica . En resumen: este patrón (anterior a Java5.0 o sin la declaración volátil) podría devolver una referencia al objeto Bar que (todavía) está en un estado incorrecto.

Este patrón fue inventado para la optimización del rendimiento. Pero esto ya no es una preocupación real. El siguiente código de inicialización diferida es rápido y, lo que es más importante, más fácil de leer.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}
Benno Richters
fuente
1
¡Lo suficientemente justo! Me siento cómodo con volátil y su uso. Ah, y tres hurras por JCiP.
Stu Thompson
1
Oh, este es aparentemente el enfoque defendido por William Pugh, de la fama FindBugz.
Stu Thompson
44
@Stu La primera edición de Effective Java (copyright 2001) detalla este patrón en el ítem 48.
Pascal Thivent
99
@Bno: ¿Qué hay de hacer que el constructor sea privado?
xyz
2
@ AlikElzin-kilaka No del todo. La instancia se crea en la fase de carga de clases para BarHolder , que se retrasa hasta la primera vez que se necesita. El constructor de Bar puede ser tan complicado como quieras, pero no se llamará hasta el primero getBar(). (Y si getBarse le llama "demasiado pronto", entonces enfrentará el mismo problema sin importar cómo se implementen los solteros.) Puede ver la carga de clase diferida del código anterior aquí: pastebin.com/iq2eayiR
Ti Strga
95

Hilo seguro en Java 5+:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

EDITAR : Presta atención al volatilemodificador aquí. :) Es importante porque sin él, el JMM (Java Memory Model) no garantiza otros hilos para ver cambios en su valor. La sincronización no se ocupa de eso, solo serializa el acceso a ese bloque de código.

EDIT 2 : la respuesta de @Bno detalla el enfoque recomendado por Bill Pugh (FindBugs) y es discutible mejor. Ve a leer y vota su respuesta también.

Stu Thompson
fuente
1
¿Dónde puedo obtener más información sobre el modificador volátil?
once81
Ver comentarios de stackoverflow.com/questions/70689/…
Pascal Thivent
2
Creo que es importante mencionar sobre los ataques de reflexión. Es cierto que la mayoría de los desarrolladores no necesitan preocuparse, pero parece que ejemplos como estos (sobre singletons basados ​​en Enum) deberían incluir un código que proteja contra ataques de instanciación múltiple o simplemente poner un descargo de responsabilidad que indique tales posibilidades.
luis.espinal
2
Aquí no se necesita una palabra clave volátil, ya que la sincronización proporciona exclusión mutua más visibilidad de la memoria.
Hemant
2
¿Por qué molestarse con todo esto en Java 5+? Tengo entendido que el enfoque enum proporciona seguridad de subprocesos e inicialización diferida. También es mucho más simple ... Además, si desea evitar una enumeración, todavía evitaría el enfoque de clase estática anidada ...
Alexandros
91

Olvídate de la inicialización perezosa , es demasiado problemático. Esta es la solución más simple:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}
Jonathan
fuente
21
La variable de instancia singleton también se puede hacer final. por ejemplo, final estático privado A singleton = new A ();
jatanp
19
Eso es efectivamente una inicialización perezosa, ya que el singleton estático no se instanciará hasta que se cargue la clase y la clase no se cargará hasta que sea necesario (lo cual será justo en el momento en que hace referencia por primera vez al método getInstance ()).
Dan Dyer
77
Si la clase A se carga mucho antes de que desee que se instancia la estática, puede envolver la estática en una clase interna estática para desacoplar la inicialización de la clase.
Tom Hawtin - tackline
3
Estoy de acuerdo en que esta respuesta es la más simple, y Anirudhan, no hay necesidad de declarar la instancia final. Ningún otro hilo tendrá acceso a la clase mientras se inicializan los miembros estáticos. esto está garantizado por el compilador, en otras palabras, toda la inicialización estática se realiza de manera sincronizada, solo un subproceso.
enor
44
Este enfoque tiene una limitación: el constructor no puede lanzar una excepción.
wangf
47

Asegúrate de que realmente lo necesitas. Haga un google para "singleton anti-pattern" para ver algunos argumentos en su contra. Supongo que no tiene nada de intrínseco, pero es solo un mecanismo para exponer algunos recursos / datos globales, así que asegúrese de que esta sea la mejor manera. En particular, he encontrado que la inyección de dependencia es más útil, especialmente si también está utilizando pruebas unitarias porque DI le permite usar recursos simulados para fines de prueba.

Neil Burroughs
fuente
se puede inyectar valores simulados con el método tradicional también, pero supongo que no es el estándar / srping manera por lo que su trabajo extra con ser única ganancia código heredado ...
tgkprog
21

Estoy desconcertado por algunas de las respuestas que sugieren DI como una alternativa al uso de singletons; Estos son conceptos no relacionados. Puede usar DI para inyectar instancias singleton o no singleton (por ejemplo, por subproceso). Al menos esto es cierto si usa Spring 2.x, no puedo hablar por otros marcos DI.

Por lo tanto, mi respuesta al OP sería (en todos, excepto el código de muestra más trivial) a:

  1. Use un marco DI como Spring, luego
  2. Haga que forme parte de su configuración DI, ya sea que sus dependencias sean singletons, ámbito de solicitud, ámbito de sesión o lo que sea.

Este enfoque le brinda una buena arquitectura desacoplada (y, por lo tanto, flexible y comprobable) donde usar un singleton es un detalle de implementación fácilmente reversible (siempre que cualquier singleton que use sea seguro para subprocesos).

Andrew Swan
fuente
44
Quizás porque la gente no está de acuerdo contigo. No te he rechazado, pero no estoy de acuerdo: creo que DI puede usarse para resolver los mismos problemas que los singletons. Esto se basa en entender que "singleton" significa "un objeto con una sola instancia a la que se accede directamente por un nombre global", en lugar de simplemente "un objeto con una sola instancia", lo que quizás sea un poco complicado.
Tom Anderson
2
Para ampliarlo un poco, considere TicketNumbererqué debe tener una única instancia global y dónde desea escribir una clase TicketIssuerque contenga una línea de código int ticketNumber = ticketNumberer.nextTicketNumber();. En el pensamiento tradicional de singleton, la línea de código anterior tendría que ser algo así TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. En el pensamiento DI, la clase tendría un constructor como public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
Tom Anderson
2
Y se convierte en el problema de otra persona llamar a ese constructor. Un marco DI lo haría con un mapa global de algún tipo; una arquitectura DI construida a mano lo haría porque el mainmétodo de la aplicación (o uno de sus secuaces) crearía la dependencia y luego llamaría al constructor. Esencialmente, el uso de una variable global (o un método global) es solo una forma simple del temido patrón localizador de servicios , y puede reemplazarse por inyección de dependencia, al igual que cualquier otro uso de ese patrón.
Tom Anderson
@TomAnderson Estoy realmente confundido en cuanto a por qué las personas 'temen' el patrón de localización del servicio. Creo que en la mayoría de los casos es excesivo o no es necesario en el mejor de los casos, sin embargo, hay casos aparentemente útiles. Con un número menor de parámetros, definitivamente se prefiere DI, pero imagina 20+. Decir que el código no está estructurado no es un argumento válido, porque a veces las agrupaciones de parámetros simplemente no tienen sentido. Además, desde la perspectiva de las pruebas unitarias, no me importa probar el servicio, solo la lógica comercial del mismo, y si está codificado correctamente, entonces sería fácil. Solo vi esta necesidad en proyectos a gran escala.
Brandon Ling
20

Considere realmente por qué necesita un singleton antes de escribirlo. Existe un debate cuasirreligioso sobre su uso que puede tropezar fácilmente si busca en Google singletons en Java.

Personalmente, trato de evitar los singletons con la mayor frecuencia posible por muchas razones, una vez más, la mayoría de las cuales se pueden encontrar buscando en google los singletons. Siento que a menudo se abusa de los singletons porque son fáciles de entender por todos, se usan como un mecanismo para obtener datos "globales" en un diseño OO y se usan porque es fácil eludir la gestión del ciclo de vida de los objetos (o realmente pensando en cómo puedes hacer A desde adentro B). Mire cosas como Inversion of Control (IoC) o Dependency Injection (DI) para un buen término medio.

Si realmente necesita uno, wikipedia tiene un buen ejemplo de una implementación adecuada de un singleton.


fuente
Convenido. Es más una clase fundamental que inicia el resto de su aplicación y, si se duplica, terminará con un caos completo (es decir, acceso único a un recurso o aplicación de seguridad). Pasar datos globales por toda su aplicación es una gran señal de alerta. Úselo cuando reconozca que realmente lo necesita.
Salvador Valencia
16

Los siguientes son 3 enfoques diferentes

1) Enum

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Doble comprobación de bloqueo / carga diferida

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3) método de fábrica estático

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}
Abhijit Gaikwad
fuente
13

Utilizo Spring Framework para administrar mis singletons. No impone el "singleton-ness" de la clase (lo cual no se puede hacer de todos modos si hay varios cargadores de clases involucrados), pero proporciona una forma realmente fácil de construir y configurar diferentes fábricas para crear diferentes tipos de objetos.

Mate
fuente
11

Wikipedia tiene algunos ejemplos de singletons, también en Java. La implementación de Java 5 parece bastante completa y es segura para subprocesos (se aplica un bloqueo de doble verificación).

macbirdie
fuente
11

Versión 1:

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

Carga perezosa, hilo seguro con bloqueo, bajo rendimiento debido a synchronized .

Versión 2:

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

Carga perezosa, hilo seguro con sin bloqueo, alto rendimiento.

coderz
fuente
10

Si no necesita carga lenta, simplemente intente

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

Si desea una carga lenta y desea que su Singleton sea seguro para subprocesos, pruebe el patrón de doble verificación

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

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

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

Como no se garantiza que el patrón de doble verificación funcione (debido a algún problema con los compiladores, no sé nada más sobre eso), también podría intentar sincronizar todo el método getInstance o crear un registro para todos sus Singletons.

Aleksi Yrttiaho
fuente
2
La primera versión es la mejor. Suponiendo que la clase no hace nada más que proporcionar un singleton, normalmente se instanciará aproximadamente en el mismo punto que el de la segunda versión debido a la carga de clase diferida.
Dan Dyer
1
La doble verificación no tiene sentido para una estática. ¿Y por qué ha hecho público el método de clonación protegida?
Tom Hawtin - tackline
1
-1 su versión de bloqueo doblemente verificado está rota.
Assylias
55
También necesita hacer su variable singletonvolatile
MyTitle
La primera versión es perezosa y segura para subprocesos.
Miha_x64
9

Yo diría Enum Singleton

Singleton usando enum en Java es generalmente una forma de declarar enum singleton. Enum singleton puede contener variable de instancia y método de instancia. En aras de la simplicidad, también tenga en cuenta que si está utilizando cualquier método de instancia de lo que necesita para garantizar la seguridad del hilo de ese método si afecta el estado del objeto.

El uso de una enumeración es muy fácil de implementar y no tiene inconvenientes con respecto a los objetos serializables, que deben ser evitados de otras maneras.

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Puede acceder a él de manera Singleton.INSTANCEmucho más fácil que llamar al getInstance()método en Singleton.

1.12 Serialización de constantes de enumeración

Las constantes de enumeración se serializan de manera diferente a los objetos serializables o externalizables ordinarios. La forma serializada de una constante enum consiste únicamente en su nombre; Los valores de campo de la constante no están presentes en el formulario. Para serializar una constante enum, ObjectOutputStreamescribe el valor devuelto por el método de nombre de la constante enum. Para deserializar una constante enum, ObjectInputStreamlee el nombre constante de la secuencia; la constante deserializada se obtiene llamando al java.lang.Enum.valueOfmétodo, pasando el tipo de enumeración de la constante junto con el nombre de la constante recibida como argumentos. Al igual que otros objetos serializables o externalizables, las constantes de enumeración pueden funcionar como objetivos de referencias posteriores que aparecen posteriormente en la secuencia de serialización.

El proceso por el cual se serializan constantes de enumeración no se pueden personalizar: cualquier clase específica writeObject, readObject, readObjectNoData, writeReplace, y readResolvemétodos definidos por los tipos enum son ignorados durante la serialización y deserialización. Del mismo modo, cualquier serialPersistentFieldso serialVersionUIDde campo declaraciones también se ignoran - todos los tipos de enumeración se fija serialVersionUIDde 0L. Documentar campos y datos serializables para tipos de enumeración es innecesario, ya que no hay variación en el tipo de datos enviados.

Citado de los documentos de Oracle

Otro problema con Singletons convencionales es que una vez que implementa la Serializableinterfaz, ya no siguen siendo Singleton porque el readObject()método siempre devuelve una nueva instancia como constructor en Java. Esto puede evitarse usando readResolve()y descartando una instancia recién creada reemplazando con singleton como se muestra a continuación

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

Esto puede volverse aún más complejo si su Clase Singleton mantiene el estado, ya que necesita hacerlos transitorios, pero con Enum Singleton, JVM garantiza la serialización.


Buena lectura

  1. Patrón Singleton
  2. Enums, Singletons y Deserialización
  3. Doble comprobación de bloqueo y el patrón Singleton
NullPoiиteя
fuente
8
There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}
Dheeraj Sachan
fuente
(1) no está ansioso, es perezoso debido al mecanismo de carga de la clase JVM.
Miha_x64
@ Miha_x64 cuando dije carga ansiosa, dije inicialización ansiosa. Si crees que ambos son iguales, entonces qué carga ansiosa. Tal vez deberías escribir un libro y corregir los errores cometidos por autores anteriores como Joshua Bloch.
Dheeraj Sachan
Java efectivo es un gran libro, pero definitivamente requiere edición.
Miha_x64
@ Miha_x64 lo que está ansioso por cargar, ¿puedes explicarlo con un ejemplo?
Dheeraj Sachan
Hacer algo "con entusiasmo" significa "lo antes posible". Por ejemplo, Hibernate admite las relaciones de carga con entusiasmo, si se requiere explícitamente.
Miha_x64
8

Puede que sea un poco tarde para el juego en esto, pero hay muchos matices en torno a la implementación de un singleton. El patrón de soporte no se puede usar en muchas situaciones. E IMO cuando se usa un volátil, también debe usar una variable local. Comencemos por el principio e iteremos sobre el problema. Verás a qué me refiero.


El primer intento podría verse más o menos así:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Aquí tenemos la clase MySingleton que tiene un miembro estático privado llamado INSTANCE, y un método estático público llamado getInstance (). La primera vez que se llama a getInstance (), el miembro INSTANCE es nulo. El flujo caerá en la condición de creación y creará una nueva instancia de la clase MySingleton. Las llamadas posteriores a getInstance () encontrarán que la variable INSTANCE ya está establecida y, por lo tanto, no crearán otra instancia de MySingleton. Esto garantiza que solo haya una instancia de MySingleton que se comparta entre todos los que llaman de getInstance ().

Pero esta implementación tiene un problema. Las aplicaciones multiproceso tendrán una condición de carrera en la creación de la instancia única. Si varios subprocesos de ejecución golpean el método getInstance () al mismo tiempo (o alrededor de ellos), cada uno verá el miembro INSTANCE como nulo. Esto dará como resultado que cada hilo cree una nueva instancia de MySingleton y, posteriormente, configure el miembro INSTANCE.


private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

Aquí hemos usado la palabra clave sincronizada en la firma del método para sincronizar el método getInstance (). Esto ciertamente arreglará nuestra condición de carrera. Los hilos ahora se bloquearán e ingresarán al método de uno en uno. Pero también crea un problema de rendimiento. Esta implementación no solo sincroniza la creación de la instancia única, sino que sincroniza todas las llamadas a getInstance (), incluidas las lecturas. Las lecturas no necesitan ser sincronizadas ya que simplemente devuelven el valor de INSTANCE. Dado que las lecturas constituirán la mayor parte de nuestras llamadas (recuerde, la creación de instancias solo ocurre en la primera llamada), incurriremos en un rendimiento innecesario al sincronizar todo el método.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

Aquí hemos movido la sincronización de la firma del método a un bloque sincronizado que envuelve la creación de la instancia de MySingleton. ¿Pero esto resuelve nuestro problema? Bueno, ya no estamos bloqueando las lecturas, pero también hemos dado un paso atrás. Varios subprocesos alcanzarán el método getInstance () aproximadamente al mismo tiempo y todos verán al miembro INSTANCE como nulo. Luego golpearán el bloque sincronizado donde se obtendrá el bloqueo y creará la instancia. Cuando ese hilo sale del bloque, los otros hilos competirán por el bloqueo, y uno por uno cada hilo caerá a través del bloque y creará una nueva instancia de nuestra clase. Así que estamos de vuelta donde comenzamos.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Aquí emitimos otro cheque DENTRO del bloque. Si el miembro de INSTANCE ya se ha configurado, omitiremos la inicialización. Esto se llama bloqueo de doble verificación.

Esto resuelve nuestro problema de instanciación múltiple. Pero una vez más, nuestra solución ha presentado otro desafío. Es posible que otros hilos no "vean" que el miembro de INSTANCE ha sido actualizado. Esto se debe a cómo Java optimiza las operaciones de memoria. Los subprocesos copian los valores originales de las variables de la memoria principal en la memoria caché de la CPU. Los cambios en los valores se escriben y leen desde ese caché. Esta es una característica de Java diseñada para optimizar el rendimiento. Pero esto crea un problema para nuestra implementación de singleton. Un segundo subproceso, que está siendo procesado por una CPU o núcleo diferente, usando un caché diferente, no verá los cambios realizados por el primero. Esto hará que el segundo subproceso vea al miembro de INSTANCE como nulo, lo que obligará a crear una nueva instancia de nuestro singleton.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Resolvemos esto usando la palabra clave volátil en la declaración del miembro de INSTANCE. Esto le indicará al compilador que siempre lea y escriba en la memoria principal y no en la memoria caché de la CPU.

Pero este simple cambio tiene un costo. Debido a que estamos evitando el caché de la CPU, tomaremos un impacto en el rendimiento cada vez que operemos en el miembro volátil INSTANCE, lo que hacemos 4 veces. Verificamos dos veces la existencia (1 y 2), establecemos el valor (3) y luego devolvemos el valor (4). Se podría argumentar que este camino es el caso marginal, ya que solo creamos la instancia durante la primera llamada del método. Quizás un golpe de rendimiento en la creación es tolerable. Pero incluso nuestro caso de uso principal, lee, operará en el miembro volátil dos veces. Una vez para verificar la existencia, y otra vez para devolver su valor.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

Dado que el impacto en el rendimiento se debe a que opera directamente en el miembro volátil, establezcamos una variable local en el valor del volátil y, en su lugar, operemos en la variable local. Esto disminuirá la cantidad de veces que operamos en el volátil, recuperando así parte de nuestro rendimiento perdido. Tenga en cuenta que tenemos que establecer nuestra variable local nuevamente cuando ingresamos al bloque sincronizado. Esto garantiza que esté actualizado con los cambios que ocurrieron mientras esperábamos el bloqueo.

Escribí un artículo sobre esto recientemente. Deconstruyendo The Singleton . Puede encontrar más información sobre estos ejemplos y un ejemplo del patrón "titular" allí. También hay un ejemplo del mundo real que muestra el enfoque volátil doblemente verificado. Espero que esto ayude.

Michael Andrews
fuente
¿Podría explicar por qué BearerToken instanceen su artículo no lo es static? Y ¿qué es result.hasExpired()?
Woland
¿Y qué class MySingletontal, tal vez debería ser final?
Woland
1
@Woland La BearerTokeninstancia no es estática porque es parte de la BearerTokenFactory configuración de un servidor de autorización específico. Podría haber muchos BearerTokenFactoryobjetos, cada uno con su propio "caché" BearerTokenque entrega hasta que haya expirado. El hasExpired()método en el BeraerTokense llama en el get()método de fábrica para garantizar que no entregue un token caducado. Si caducó, se solicitará un nuevo token para el servidor de autorización. El párrafo que sigue al bloque de código explica esto con mayor detalle.
Michael Andrews
4

Así es como implementar un simple singleton:

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Así es como crear la pereza correctamente singleton:

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
Nicolas Filotto
fuente
Ambos son vagos, suponiendo que lo único que necesita de singleton es su instancia.
Miha_x64
@ Miha_x64 el primer caso instanciará el singleton cuando la JVM inicialice la clase, el segundo caso solo instanciará el singleton cuando llame getInstance(). Pero, de hecho, si no tiene ningún otro método estático en su clase Singletony solo llama, getInstance()no hay una diferencia real.
Nicolas Filotto
3

Necesita un lenguaje de verificación doble si necesita cargar la variable de instancia de una clase perezosamente. Si necesita cargar una variable estática o un singleton de manera perezosa, necesita un idioma de titularización de inicialización bajo demanda .

Además, si el singleton necesita ser serilizable, todos los demás campos deben ser transitorios y el método readResolve () debe implementarse para mantener el objeto singleton invariable. De lo contrario, cada vez que se deserialice el objeto, se creará una nueva instancia del objeto. Lo que readResolve () hace es reemplazar el nuevo objeto leído por readObject (), lo que obligó a ese nuevo objeto a ser recolectado como basura ya que no hay ninguna variable que se refiera a él.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 
Onur
fuente
3

Varias formas de hacer un objeto singleton:

  1. Según Joshua Bloch, Enum sería el mejor.

  2. También puede utilizar el bloqueo de doble verificación.

  3. Incluso se puede usar la clase estática interna.

Shailendra Singh
fuente
3

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

Doble control de bloqueo

Si desea codificar un singleton "clásico" que funciona en un entorno multiproceso (a partir de Java 1.5), debe utilizar 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 1.5 porque la implementación de la palabra clave volátil fue diferente.

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

Esta implementación instancia el singleton cuando se carga la clase y proporciona seguridad de 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 moldavo
fuente
2

Para JSE 5.0 y superior, adopte el enfoque Enum; de lo contrario, utilice el enfoque de soporte singleton estático ((un enfoque de carga diferida descrito por Bill Pugh). La última solución también es segura para subprocesos sin requerir construcciones de lenguaje especiales (es decir, volátil o sincronizado).

raoadnan
fuente
2

Otro argumento a menudo utilizado contra Singletons son sus problemas de comprobabilidad. Los singletons no se pueden burlar fácilmente con fines de prueba. Si esto resulta ser un problema, me gustaría hacer la siguiente pequeña modificación:

public class SingletonImpl {

    private static SingletonImpl instance;

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

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

El setInstancemétodo agregado permite configurar una implementación de maqueta de la clase singleton durante las pruebas:

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Esto también funciona con enfoques de inicialización temprana:

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Esto tiene el inconveniente de exponer esta funcionalidad a la aplicación normal también. Otros desarrolladores que trabajan en ese código podrían verse tentados a usar el método 'setInstance' para alterar, alterar una función específica y, por lo tanto, cambiar el comportamiento completo de la aplicación, por lo tanto, este método debe contener al menos una buena advertencia en su javadoc.

Aún así, para la posibilidad de realizar pruebas de prueba (cuando sea necesario), esta exposición del código puede ser un precio aceptable a pagar.

usuario3792852
fuente
1

clase singleton más simple

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}
rohan kamat
fuente
1
esta es la misma que la respuesta de Jonathan abajo
enor
1
Duplicado de la respuesta de este hermano por Jonathan publicado cinco años antes. Vea esa respuesta para comentarios interesantes.
Basil Bourque
0

Todavía creo que después de Java 1.5, enum es la mejor implementación de singleton disponible, ya que también garantiza que incluso en entornos de subprocesos múltiples, solo se crea una instancia.

public enum Singleton{ INSTANCE; }

y listo!

shikjohari
fuente
1
Esto ya se menciona en otras respuestas hace años.
Pang
0

Echa un vistazo a esta publicación.

Ejemplos de patrones de diseño de GoF en las bibliotecas principales de Java

De la sección "Singleton" de la mejor respuesta,

Singleton (reconocible por métodos de creación que devuelven la misma instancia (generalmente de sí mismo) cada vez)

  • java.lang.Runtime # getRuntime ()
  • java.awt.Desktop # getDesktop ()
  • java.lang.System # getSecurityManager ()

También puede aprender el ejemplo de Singleton de las clases nativas de Java.

kenju
fuente
0

El mejor patrón singleton que he visto utiliza la interfaz del proveedor.

  • Es genérico y reutilizable.
  • Es compatible con la inicialización perezosa
  • Solo se sincroniza hasta que se haya inicializado, luego el proveedor bloqueante se reemplaza por un proveedor no bloqueante.

Vea abajo:

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}

fuente
-3

A veces un simple " static Foo foo = new Foo();" no es suficiente. Solo piense en la inserción de datos básicos que desea hacer.

Por otro lado, tendría que sincronizar cualquier método que instanciara la variable singleton como tal. La sincronización no es mala como tal, pero puede conducir a problemas de rendimiento o bloqueo (en situaciones muy raras con este ejemplo. La solución es

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

Ahora que pasa? La clase se carga a través del cargador de clases. Directamente después de que la clase fue interpretada desde una matriz de bytes, la VM ejecuta el bloque estático {} . ese es todo el secreto: el bloque estático solo se llama una vez, el tiempo en que la clase (nombre) del paquete dado es cargada por este cargador de clase.

Georgi
fuente
3
No es verdad. Las variables estáticas se inicializan junto con los bloques estáticos cuando se carga la clase. No es necesario dividir la declaración.
Craig P. Motlin el
-5
public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

Como hemos agregado la palabra clave Sincronizada antes de getInstance, hemos evitado la condición de carrera en el caso en que dos hilos llaman a getInstance al mismo tiempo.

somenath mukhopadhyay
fuente