¿Cuál es una forma eficiente de implementar un patrón singleton en Java?
java
singleton
design-patterns
Riyaz Mohammed Ibrahim
fuente
fuente
Respuestas:
Use una enumeración:
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 ):
Editar: Una parte en línea de "Java efectivo" dice:
fuente
Dependiendo del uso, hay varias respuestas "correctas".
Desde java5, la mejor manera de hacerlo es usar una enumeración:
Pre java5, el caso más simple es:
Repasemos el código. Primero, quieres que la clase sea final. En este caso, he usado la
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 del constructor evita que los usuarios usen la reflexión para crear un segundo Foo. Luego, crea unprivate static final Foo
campo para contener la única instancia y unpublic 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 class
para cargar la instancia. El código entonces se vería así: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.
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.fuente
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
Todo está bien, excepto su singleton cargado temprano. Probemos singleton cargado perezoso
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
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
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
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
Sí, este es nuestro mismo héroe :)
Desde la línea
private static final Foo INSTANCE = new Foo();
solo se ejecuta cuando la claseFooLoader
se 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
Que internamente será tratado como
¡Eso es! No más miedo a la serialización, hilos y código feo. También ENUMS singleton están perezosamente inicializados .
-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 .
fuente
serialVersionUID
de0L
. 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.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.
fuente
getBar()
. (Y sigetBar
se 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/iq2eayiRHilo seguro en Java 5+:
EDITAR : Presta atención al
volatile
modificador 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.
fuente
Olvídate de la inicialización perezosa , es demasiado problemático. Esta es la solución más simple:
fuente
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.
fuente
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:
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).
fuente
TicketNumberer
qué debe tener una única instancia global y dónde desea escribir una claseTicketIssuer
que contenga una línea de códigoint 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 comopublic TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.main
mé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.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
Los siguientes son 3 enfoques diferentes
1) Enum
2) Doble comprobación de bloqueo / carga diferida
3) método de fábrica estático
fuente
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.
fuente
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).
fuente
Versión 1:
Carga perezosa, hilo seguro con bloqueo, bajo rendimiento debido a
synchronized
.Versión 2:
Carga perezosa, hilo seguro con sin bloqueo, alto rendimiento.
fuente
Si no necesita carga lenta, simplemente intente
Si desea una carga lenta y desea que su Singleton sea seguro para subprocesos, pruebe el patrón de doble verificación
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.
fuente
volatile
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.
Puede acceder a él de manera
Singleton.INSTANCE
mucho más fácil que llamar algetInstance()
método en Singleton.Otro problema con Singletons convencionales es que una vez que implementa la
Serializable
interfaz, ya no siguen siendo Singleton porque elreadObject()
método siempre devuelve una nueva instancia como constructor en Java. Esto puede evitarse usandoreadResolve()
y descartando una instancia recién creada reemplazando con singleton como se muestra a continuaciónEsto 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
fuente
fuente
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í:
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.
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.
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.
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.
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.
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.
fuente
BearerToken instance
en su artículo no lo esstatic
? Y ¿qué esresult.hasExpired()
?class MySingleton
tal, tal vez debería serfinal
?BearerToken
instancia no es estática porque es parte de laBearerTokenFactory
configuración de un servidor de autorización específico. Podría haber muchosBearerTokenFactory
objetos, cada uno con su propio "caché"BearerToken
que entrega hasta que haya expirado. ElhasExpired()
método en elBeraerToken
se llama en elget()
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.Así es como implementar un simple
singleton
:Así es como crear la pereza correctamente
singleton
:fuente
getInstance()
. Pero, de hecho, si no tiene ningún otro método estático en su claseSingleton
y solo llama,getInstance()
no hay una diferencia real.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.
fuente
Varias formas de hacer un objeto singleton:
Según Joshua Bloch, Enum sería el mejor.
También puede utilizar el bloqueo de doble verificación.
Incluso se puede usar la clase estática interna.
fuente
Enum singleton
La forma más sencilla de implementar un Singleton que sea seguro para subprocesos es usar un Enum
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.
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.
fuente
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).
fuente
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:
El
setInstance
método agregado permite configurar una implementación de maqueta de la clase singleton durante las pruebas:Esto también funciona con enfoques de inicialización temprana:
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.
fuente
clase singleton más simple
fuente
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!
fuente
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,
También puede aprender el ejemplo de Singleton de las clases nativas de Java.
fuente
El mejor patrón singleton que he visto utiliza la interfaz del proveedor.
Vea abajo:
fuente
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
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.
fuente
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.
fuente