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


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


724
2017-09-16 09:24


origen


Respuestas:


Use una enumeración:

public enum Foo {
    INSTANCE;
}

Joshua Bloch explicó este enfoque en su Efectivo Java Reloaded hablar en Google I / O 2008: enlace al video. También vea las diapositivas 30-32 de su presentación (effective_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: Un porción 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 ofrece una garantía inamovible contra la creación de instancias múltiples, incluso frente a la serialización sofisticada o los ataques de reflexión. aún no se ha adoptado ampliamente, un tipo de enumeración de elemento único es la mejor manera de implementar un singleton"


716
2017-09-16 11:31



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 el final palabra 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 desde el constructor evita que los usuarios usen la reflexión para crear un segundo Foo. Entonces creas un private static final Foo campo para contener la única instancia, y un public static Foo getInstance() método para devolverlo. La especificación de Java se asegura de que el constructor solo se invoque cuando la clase se utiliza 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 necesita usar la inicialización diferida.

Puedes usar un private static class para cargar la instancia El código 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;
    }
}

Desde la linea private static final Foo INSTANCE = new Foo(); solo se ejecuta cuando se usa realmente la clase FooLoader, esto se ocupa de la instanciación lenta y garantiza la seguridad de la ejecución de subprocesos.

Cuando también desee 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.


218
2017-09-16 15:44



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 es difícil entender por qué es necesario. Sin el volátil, este código ya no sería seguro para subprocesos debido al antipatrón con bloqueo comprobado. Ver más sobre esto en el párrafo 16.2.4 de 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 está (aún) 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 diferido 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;
    }
}

119
2017-09-16 12:24



Renuncia: Acabo de resumir todas las impresionantes respuestas y las escribí en mis palabras.


Mientras implementamos Singleton tenemos 2 opciones
1. Carga lenta
2. Carga temprana

La carga lenta agrega un poco de sobrecarga (muchas cosas para ser sincero) así que úsala solo si tienes un objeto muy grande o un código de construcción pesado Y también tienes otros métodos o campos estáticos accesibles que pueden usarse antes de que se necesite una instancia, entonces y solo entonces necesita utilizar la inicialización diferida. De lo contrario, elegir una carga anticipada es una buena opción.

La forma más simple 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. Vamos a probar 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 muchos ejemplos de nuestro héroe. Así que vamos a protegerlo del mal multi threading

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 a un héroe, realmente! 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 "Lenguaje de bloqueo controlado doblemente". Es fácil olvidar la declaración volátil y es difícil entender por qué es necesario.
Para detalles : http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Ahora estamos seguros sobre el hilo malo, pero ¿qué pasa con la cruel serialización? Tenemos que asegurarnos de que, incluso mientras no se serializa, no se crea 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 los hilos y la serialización, pero nuestro código se ve voluminoso y feo. Vamos a darle una vuelta 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 linea private static final Foo INSTANCE = new Foo(); solo se ejecuta cuando la clase FooLoader en realidad se usa, esto se ocupa de la instanciación perezosa,

y está garantizado que es 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 todo el miedo a la serialización, los hilos y el código feo. también ENUMS singleton se inicializan lentamente.

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 blindada contra múltiples   instanciación, incluso frente a la serialización sofisticada o   ataques de reflexión. Si bien este enfoque aún no se ha adoptado ampliamente,   un tipo de enumeración de elemento único es la mejor manera de implementar un singleton.

-Joshua Bloch en "Java efectivo"

Ahora es posible que se haya dado cuenta de por qué los ENUMS se consideran la mejor manera de implementar Singleton y gracias por su paciencia :)
Lo actualicé en mi Blog.


119
2018-05-16 06:24



Thread safe 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 a volatile modificador aquí. :) Es importante porque sin él, el JMM (Modelo de memoria Java) no garantiza otros subprocesos para ver los cambios en su valor. La sincronización no ocúpate 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.


93
2017-09-16 09:51



Olvidar inicialización lenta, es muy 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;
    }
}

86
2017-09-16 09:49



Asegúrate de que realmente lo necesites. Haz un google para "antipatrón único" para ver algunos argumentos en contra de eso. No hay nada intrínsecamente malo en ello, supongo, 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á usando pruebas unitarias porque DI le permite utilizar recursos burlados para fines de prueba.


45
2017-09-16 09:33