¿Cuál es una manera eficiente de implementar un patrón de singleton en Java?
Utilice una enumeración:
public enum Foo {
INSTANCE;
}
Joshua Bloch explicó este enfoque en su Effective Java Reloaded talk en Google I/O 2008: enlace a 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: Una parte en línea de "Java efectiva" 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 férrea contra la creación de instancias múltiples, incluso frente a la serialización sofisticada o los ataques de reflexión. aún por ser ampliamente adoptado, un tipo de enumeración de un solo elemento es la mejor manera de implementar un singleton ".
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");
}
}
Vamos a repasar el código. Primero, quieres que la clase sea final. En este caso, he usado la palabra clave final
para que los usuarios sepan que es definitiva. Luego debes 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. Luego crea un campo private static final Foo
para contener la única instancia, y un método public static Foo getInstance()
para devolverlo. La especificación de Java se asegura de que solo se llame al constructor cuando la clase se usa por primera vez.
Cuando tiene un objeto muy grande o un código de construcción pesada Y también tiene otros métodos o campos estáticos accesibles que podrían usarse antes de que se necesite una instancia, entonces y solo entonces debe usar una inicialización perezosa.
Puede 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;
}
}
Dado que la línea private static final Foo INSTANCE = new Foo();
solo se ejecuta cuando se utiliza realmente la clase FooLoader, esto se encarga de la creación de instancias perezosa, y se garantiza que es seguro 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 haya serializado en una ejecución anterior de su programa.
Descargo de responsabilidad: Acabo de resumir todas las respuestas asombrosas y las escribí en mis palabras.
Mientras implementamos Singleton tenemos 2 opciones.
1. Carga lenta
2. Carga temprana
La carga diferida agrega una sobrecarga de bits (muchos, para ser honesto), así que úselo solo cuando tenga un objeto muy grande o un código de construcción pesada Y también tenga otros métodos o campos estáticos accesibles que podrían usarse antes de que se necesite una instancia, entonces y solo entonces debe utilizar una inicialización lenta. De lo contrario, elegir una 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 es bueno, excepto su singleton cargado temprano. Vamos a probar perezoso cargado singleton
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 hilos.
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 denomina "lenguaje de bloqueo de doble comprobación". 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 del hilo malvado, pero ¿qué pasa con la cruel serialización? Tenemos que asegurarnos de que, aunque no se cree un nuevo objeto, se elimine la serialización.
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 haya serializado en una ejecución anterior de nuestro programa.
Finalmente, hemos agregado suficiente protección contra los subprocesos y la serialización, pero nuestro código parece 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 :)
Dado que la línea private static final Foo INSTANCE = new Foo();
solo se ejecuta cuando la clase FooLoader
se usa realmente, esto se encarga de la instanciación perezosa,
y está garantizado para ser seguro para subprocesos.
Y hemos llegado hasta aquí, esta es la mejor manera de lograr que todo lo que hicimos sea 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 no es más miedo a la serialización, hilos y código feo. También ENUMS singleton están inicializados perezosamente .
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 férrea contra la creación de instancias múltiples, incluso ante la serialización sofisticada o los ataques de reflexión. 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 efectiva"
Ahora es posible que se haya dado cuenta de por qué ENUMS se considera la mejor manera de implementar Singleton y gracias por su paciencia :)
Se actualizó en mi blog .
La solución publicada por Stu Thompson es válida en Java5.0 y posteriores. 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 la volatilidad, este código ya no sería seguro para subprocesos debido a la antipaterna de bloqueo de doble verificación. Vea más sobre esto en el párrafo 16.2.4 de Java Concurrency in Practice . En resumen: este patrón (antes de 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 realmente una preocupación real. El siguiente código de inicialización perezoso 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;
}
}
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: Preste atención al modificador volatile
aquí. :) Es importante porque sin él, el JMM (Modelo de Memoria de Java) no garantiza otros subprocesos para ver los cambios en su valor. La sincronización no lo hace cuida 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 más discutible. Ve a leer y vota su respuesta también.
Olvide inicialización lenta , 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;
}
}
Asegúrese de que realmente lo necesita. Haga un google para "anti-patrón de singleton" para ver algunos argumentos en contra. Supongo que no hay nada malo en ello, 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 usar recursos simulados para propósitos de prueba.
No olvide que el Singleton es solo un Singleton para el Classloader que lo cargó. Si está utilizando varios cargadores (Contenedores), cada PODRÍA tener su propia versión del Singleton.
Estoy desconcertado por algunas de las respuestas que sugieren DI como alternativa al uso de singletons; Estos son conceptos no relacionados. Puede usar DI para inyectar instancias singleton o no singleton (por ejemplo, por hilo). Al menos esto es cierto si usa Spring 2.x, no puedo hablar por otros marcos DI.
Así que mi respuesta al OP sería (en todos menos el código de muestra más trivial):
Este enfoque le brinda una arquitectura agradable desacoplada (y, por lo tanto, flexible y comprobable) en la que utilizar un singleton es un detalle de implementación fácilmente reversible (siempre que cualquier singleton que use sea seguro para hilos).
Realmente considere por qué necesita un singleton antes de escribirlo. Existe un debate cuasi religioso sobre su uso que puede fácilmente tropezar 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 al buscar en Google singletons. Creo 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 Evite la administración del ciclo de vida de los objetos (o realmente piense cómo puede hacer A desde dentro de B). Mire cosas como la Inversión de control (IoC) o la Inyección de dependencia (DI) para un medio medio agradable.
Si realmente necesita uno, wikipedia tiene un buen ejemplo de una implementación correcta de un singleton.
Los siguientes son 3 enfoques diferentes
1) Enum
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Doble control de bloqueo/carga perezosa
/**
* 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;
}
}
Uso Spring Framework para gestionar mis singletons. No impone el "carácter único" de la clase (lo que no se puede hacer de todos modos si hay varios cargadores de clases involucrados), pero proporciona una manera realmente fácil de construir y configurar diferentes fábricas para crear diferentes tipos de objetos.
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 no bloqueo, alto rendimiento.
Wikipedia tiene algunos ejemplos de singletons, también en Java. La implementación de Java 5 se ve bastante completa, y es segura para subprocesos (se aplica un doble control de bloqueo).
Si no necesita carga perezosa, 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 comprobació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 (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.
Puede que sea un poco tarde para el juego, pero hay muchos matices en la implementación de un singleton. El patrón de soporte no se puede utilizar en muchas situaciones. Y la OMI cuando se utiliza un volátil, también debe utilizar una variable local. Empecemos por el principio y repasemos el problema. Verás lo que quiero decir.
El primer intento puede parecer algo como esto:
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á entonces 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á otra instancia de MySingleton. Esto garantiza que solo hay una instancia de MySingleton que se comparte entre todos los que llaman a getInstance ().
Pero esta implementación tiene un problema. Las aplicaciones de subprocesos múltiples 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 () en (o alrededor) al mismo tiempo, cada uno verá al miembro INSTANCE como nulo. Esto dará como resultado que cada hilo cree una nueva instancia de MySingleton y luego configure el miembro INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Aquí hemos utilizado 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 el 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 también sincroniza todas las llamadas a getInstance (), incluidas las lecturas. Las lecturas no necesitan estar 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 impacto de 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 golpeará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 se creará la instancia. Cuando ese hilo salga 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 empezamos.
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 desde el interior del bloque. Si el miembro INSTANCE ya se ha establecido, omitiremos la inicialización. Esto se llama bloqueo de doble control.
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 subprocesos no "vean" que el miembro INSTANCE se ha actualizado. Esto se debe a cómo Java optimiza las operaciones de memoria. Los hilos 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 se leen desde esa memoria 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, utilizando un caché diferente, no verá los cambios realizados por el primero. Esto hará que el segundo hilo vea al miembro INSTANCE como nulo, lo que obliga 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 utilizando la palabra clave volátil en la declaración del miembro INSTANCE. Esto le dirá 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 la memoria caché de la CPU, cada vez que operamos en el miembro volátil de INSTANCE tendremos un impacto en el rendimiento, lo que hacemos 4 veces. Verificamos la existencia (1 y 2), establecemos el valor (3) y luego devolvemos el valor (4). Se podría argumentar que esta ruta es el caso marginal, ya que solo creamos la instancia durante la primera llamada del método. Tal vez un golpe de rendimiento en la creación es tolerable. Pero incluso nuestro principal caso de uso, las lecturas, operarán en el miembro volátil dos veces. Una vez para comprobar 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 la operación directa en el miembro volátil, establezcamos una variable local al valor de la volatilidad y operemos en la variable local en su lugar. Esto disminuirá el número de veces que operamos en el volátil, recuperando así parte de nuestra pérdida de rendimiento. Tenga en cuenta que debemos configurar nuestra variable local nuevamente cuando ingresemos al bloque sincronizado. Esto garantiza que esté actualizado con cualquier cambio que haya ocurrido mientras esperábamos el bloqueo.
Escribí un artículo sobre esto recientemente. Deconstruyendo El Singleton . Puede encontrar más información sobre estos ejemplos y un ejemplo del patrón de "soporte" allí. También hay un ejemplo del mundo real que muestra el enfoque volátil de doble control. Espero que esto ayude.
Yo diría Enum Singleton
Singleton utilizando enum en Java es generalmente una forma de declarar singleton enum. Enum singleton puede contener una variable de instancia y un método de instancia. Para simplificar, también tenga en cuenta que si está utilizando un método de instancia del que necesita para garantizar la seguridad de subprocesos de ese método, en todo caso, 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 evitarse 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 por Singleton.INSTANCE
, mucho más fácil que llamar al método getInstance()
en Singleton.
1.12 Serialización de Enum Constantes
Las constantes de enumeración se serializan de manera diferente a los objetos serializables o externalizables comunes. La forma serializada de una constante de enumeración consiste únicamente en su nombre; Los valores de campo de la constante no están presentes en el formulario. Para serializar una constante enum,
ObjectOutputStream
escribe el valor devuelto por el método de nombre de la constante enum. Para deserializar una constante de enumeración,ObjectInputStream
lee el nombre constante de la secuencia; la constante deserializada se obtiene luego llamando al métodoJava.lang.Enum.valueOf
, 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 el flujo de serialización.El proceso mediante el cual las constantes de enumeración se serializan no se puede personalizar: cualquier método específico de clase
writeObject
,readObject
,readObjectNoData
,writeReplace
yreadResolve
definidos por los tipos de enum son ignorados durante la serialización y deserialización. De manera similar, cualquier declaración de camposerialPersistentFields
oserialVersionUID
también se ignoran: todos los tipos de enumeración tienen unaserialVersionUID
fija de0L
. Documentar campos y datos serializables para tipos de enumeración es innecesario, ya que no hay variación en el tipo de datos enviados.
Otro problema con los Singletons convencionales es que una vez que implementas la interfaz Serializable
, ya no permanecen Singleton porque el método readObject()
siempre devuelve una nueva instancia como constructor en Java. Esto se puede evitar usando readResolve()
y descartando la instancia recién creada reemplazando con singleton como abajo
// 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, la serialización está garantizada por JVM.
Buena lectura
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");
}
}
Así es como implementar una 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;
}
}
Esto es cómo crear correctamente tu 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();
}
}
Necesita doble verificación idiom si necesita cargar la variable de instancia de una clase perezosamente. Si necesita cargar una variable estática o un singleton perezosamente, necesita titular de la demanda bajo demanda idioma.
Además, si el singleton necesita ser seriable, todos los demás campos deben ser transitorios y el método readResolve () debe implementarse para mantener invariante el objeto singleton. De lo contrario, cada vez que se deserializa el objeto, se creará una nueva instancia del objeto. Lo que hace readResolve () es reemplazar el nuevo objeto leído por readObject (), lo que obligó a que el nuevo objeto se recolectara 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.
}
Varias formas de hacer objeto singleton:
Según Joshua Bloch - Enum sería el mejor.
también puede utilizar el bloqueo de doble control.
Incluso la clase estática interna puede ser usada.
Enlet singleton
La forma más sencilla de implementar un Singleton que es 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 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 la ejecución de subprocesos antes de 1.5 porque la implementación de la palabra clave volátil fue 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 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");
}
}
Para JSE 5.0 y superior, utilice el enfoque Enum, de lo contrario, use el enfoque de soporte de un solo elemento estático ((un método de carga lenta descrito por Bill Pugh). La última solución también es segura para subprocesos sin necesidad de construcciones de lenguaje especiales (es decir, volátil o sincronizada).
Otro argumento usado a menudo contra Singletons son sus problemas de probabilidad. Singletons no son fácilmente simulables para propósitos de prueba. Si esto resulta ser un problema, me gustaría hacer la siguiente modificación leve:
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 método setInstance
agregado permite configurar una implementación simulada 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 los 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 la desventaja 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 una función específica y, por lo tanto, cambiar todo el comportamiento de la aplicación, por lo tanto este método debería contener al menos una buena advertencia en su javadoc.
Aún así, ante la posibilidad de realizar pruebas de simulación (cuando sea necesario), la exposición de este código puede ser un precio aceptable para pagar.
clase de singleton más simple
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Sigo pensando que después de Java 1.5, enum es la mejor implementación de singleton disponible, ya que también garantiza que incluso en los entornos de múltiples subprocesos, solo se crea una instancia.
public enum Singleton{ INSTANCE; }
y ya terminaste !!!
Echa un vistazo a este post.
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 a partir de las clases nativas de Java.
El mejor patrón de singleton que he visto utiliza la interfaz de Proveedor.
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();
}
}