patrones - ¿Cuál es una manera eficiente de implementar un patrón de singleton en Java?
patron singleton java example (30)
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.
¿Cuál es una forma eficiente de implementar un patrón de singleton en Java?
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 creas 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 una 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 línea private static final Foo INSTANCE = new Foo();
solo se ejecuta cuando la clase FooLoader se usa realmente, esto cuida la instanciación 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.
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):
- Use un marco DI como Spring, entonces
- Hágalo parte de su configuración de DI, ya sea que sus dependencias sean singletons, request scoped, session scoped, o lo que sea.
Este enfoque le brinda una buena arquitectura 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 subprocesos).
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 Java) no garantiza otros subprocesos para ver los cambios en su valor. La sincronización no se encarga 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 mejor argumentable. Ve a leer y vota su respuesta también.
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 al bloqueo antipatternado de doble comprobació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;
}
}
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;
}
}
No olvide que 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.
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;
}
}
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. E IMO 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 pasará por el 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 de 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 que opera directamente en el miembro volátil, configuremos una variable local al valor de la volatilidad y operemos en la variable local. 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 asegura 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.
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 en Google. 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 eludir la administració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 medio medio agradable.
Si realmente necesita uno, wikipedia tiene un buen ejemplo de una implementación adecuada de un singleton.
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 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.
Utilice una enumeración:
public enum Foo {
INSTANCE;
}
Joshua Bloch explicó este enfoque en su charla Effective Java Reloaded 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)); } }
Edición: 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 ante la serialización sofisticada o los ataques de reflexión. Aunque este enfoque ha aún por ser ampliamente adoptado, un tipo de enumeración de un solo elemento es la mejor manera de implementar un singleton ".
Utilizo 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 clase involucrados), pero proporciona una manera realmente fácil de construir y configurar diferentes fábricas para crear diferentes tipos de objetos.
Wikipedia tiene algunos examples 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).
Yo diría que 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. Por motivos de simplicidad, 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 mediante 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 del nombre de la constante enum. Para deserializar una constante de enumeración,ObjectInputStream
lee el nombre de la 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: los
writeObject
,readObject
,readObjectNoData
,writeReplace
yreadResolve
específicos de la clase definidos por los tipos de enumeración se ignoran durante la serialización y deserialización. Del mismo modo, cualquier declaración de camposerialPersistentFields
oserialVersionUID
también se ignoran: todos los tipos de enumeración tienen unserialVersionUID
fijo deserialVersionUID
. 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 como 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
Descargo de responsabilidad: acabo de resumir todas las respuestas impresionantes y lo escribí en mis palabras.
Mientras implementamos Singleton tenemos 2 opciones.
1. carga perezosa
2. Carga temprana
La carga diferida agrega una sobrecarga de bits (mucho 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 de múltiples hilos malvados.
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 "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 más 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 serializó 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 a nuestro héroe una renovación.
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í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 se inician 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 :)
Lo actualicé en mi blog .
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 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");
}
}
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 la 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.
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 creacionales 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 usa 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 de bloqueo se reemplaza por un proveedor sin bloqueo.
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();
}
}
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)
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 los entornos de múltiples subprocesos, solo se crea una instancia.
public enum Singleton{ INSTANCE; }
y ya terminaste !!!
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.
clase de singleton más simple
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
A veces un simple " static Foo foo = new Foo();
" no es suficiente. Solo piensa en la inserción de datos básicos que quieres hacer.
Por otro lado, tendría que sincronizar cualquier método que cree una instancia de la variable singleton como tal. La sincronización no es mala como tal, pero puede llevar a problemas de rendimiento o bloqueo (en situaciones muy raras usando 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 máquina virtual ejecuta el bloque {} estático . ese es todo el secreto: el bloque estático solo se llama una vez, la hora en que este cargador de clases carga la clase (nombre) dada del paquete dado.
Así es como se implementa 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;
}
}
Esta es la forma correcta de crear perezoso 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();
}
}
Necesitas double-checking idioma si necesitas cargar la variable de instancia de una clase perezosamente. Si necesita cargar una variable estática o un singleton perezosamente, necesita la iniciación en el idioma del titular de la demanda .
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 lo refiera.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
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 setInstance
mé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 los primeros enfoques de inicialización:
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 cambiar así 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), este código de exposición puede ser un precio aceptable para pagar.
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");
}
}
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 subprocesos llaman a la getInstance al mismo tiempo.