¿Qué es un singleton?
Un singleton es uno de los patrones de diseño más simples que se pueden aplicar a una clase. A veces la gente dice "esta clase es un singleton", lo que significa que esta clase implementa el patrón de diseño singleton. A veces es necesario escribir una clase para la que sólo se pueda crear un objeto. Por ejemplo, una clase responsable de iniciar sesión o conectarse a una base de datos. El patrón de diseño Singleton describe cómo podemos realizar dicha tarea. Un singleton es un patrón de diseño que hace dos cosas:-
Proporciona una garantía de que una clase tendrá solo una instancia de la clase.
-
Proporciona un punto de acceso global a una instancia de esta clase.
-
Constructor privado. Restringe la capacidad de crear objetos de clase fuera de la propia clase.
-
Un método estático público que devuelve una instancia de la clase. Este método se llama
getInstance
. Este es el punto de acceso global a la instancia de clase.
Opciones de implementación
El patrón de diseño singleton se utiliza de diferentes maneras. Cada opción es buena y mala a su manera. Aquí, como siempre: no existe el ideal, pero hay que esforzarse por alcanzarlo. Pero antes que nada, definamos qué es bueno y qué es malo, y qué métricas influyen en la evaluación de la implementación de un patrón de diseño. Empecemos por lo positivo. Estos son los criterios que dan jugosidad y atractivo a la implementación:-
Inicialización diferida: cuando se carga una clase mientras la aplicación se ejecuta exactamente cuando es necesaria.
-
Simplicidad y transparencia del código: la métrica, por supuesto, es subjetiva, pero importante.
-
Seguridad de subprocesos: funciona correctamente en un entorno multiproceso.
-
Alto rendimiento en un entorno de subprocesos múltiples: los subprocesos se bloquean entre sí mínimamente o no se bloquean en absoluto cuando comparten un recurso.
-
Inicialización no diferida: cuando una clase se carga cuando se inicia la aplicación, independientemente de si es necesaria o no (una paradoja, en el mundo de las tecnologías de la información es mejor ser vago)
-
Complejidad y mala legibilidad del código. La métrica también es subjetiva. Supondremos que si la sangre sale de los ojos, la implementación es regular.
-
Falta de seguridad del hilo. En otras palabras, "peligro del hilo". Funcionamiento incorrecto en un entorno multiproceso.
-
Rendimiento deficiente en un entorno de subprocesos múltiples: los subprocesos se bloquean entre sí todo el tiempo o con frecuencia cuando comparten un recurso.
Código
Ahora estamos listos para considerar varias opciones de implementación, enumerando los pros y los contras:Solución simple
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
La implementación más simple. Ventajas:
-
Simplicidad y transparencia del código.
-
Seguridad del hilo
-
Alto rendimiento en un entorno multiproceso
- Inicialización no diferida.
Inicialización diferida
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Ventajas:
-
Inicialización diferida.
-
No es seguro para subprocesos
Accesorio sincronizado
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Ventajas:
-
Inicialización diferida.
-
Seguridad del hilo
-
Mal rendimiento en un entorno de subprocesos múltiples
getInstance
está sincronizado y solo puedes ingresarlo uno a la vez. De hecho, no necesitamos sincronizar todo el método, sino solo la parte en la que inicializamos un nuevo objeto de clase. Pero no podemos simplemente envolver synchronized
la parte responsable de crear un nuevo objeto en un bloque: esto no proporcionará seguridad para los subprocesos. Es un poco más complicado. El método de sincronización correcto se proporciona a continuación:
Bloqueo doblemente comprobado.
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Ventajas:
-
Inicialización diferida.
-
Seguridad del hilo
-
Alto rendimiento en un entorno multiproceso
-
No es compatible con versiones de Java inferiores a 1.5 (la palabra clave volátil se corrigió en la versión 1.5)
INSTANCE
debe ser final
, o volatile
. La última implementación que discutiremos hoy es Class Holder Singleton
.
Titular de la clase Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Ventajas:
-
Inicialización diferida.
-
Seguridad del hilo.
-
Alto rendimiento en un entorno multiproceso.
-
Para un correcto funcionamiento es necesario garantizar que el objeto de clase
Singleton
se inicialice sin errores. De lo contrario, la primera llamada al métodogetInstance
terminará en un errorExceptionInInitializerError
y todas las siguientes fallaránNoClassDefFoundError
.
Implementación | Inicialización diferida | Seguridad del hilo | Velocidad de subprocesos múltiples | ¿Cuándo usar? |
---|---|---|---|---|
Solución simple | - | + | Rápido | Nunca. O cuando la inicialización diferida no es importante. Pero nunca mejor dicho. |
Inicialización diferida | + | - | No aplica | Siempre cuando no se necesita subprocesos múltiples |
Accesorio sincronizado | + | + | Despacio | Nunca. O cuando no importa la velocidad de trabajo con subprocesos múltiples. pero nunca mejor dicho |
Bloqueo doblemente comprobado. | + | + | Rápido | En casos excepcionales, es necesario manejar excepciones al crear un singleton. (cuando el Titular de Clase Singleton no es aplicable) |
Titular de la clase Singleton | + | + | Rápido | Siempre cuando se necesita subprocesos múltiples y existe la garantía de que se creará un objeto de clase singleton sin problemas. |
Pros y contras del patrón Singleton
En general, el singleton hace exactamente lo que se espera de él:-
Proporciona una garantía de que una clase tendrá solo una instancia de la clase.
-
Proporciona un punto de acceso global a una instancia de esta clase.
-
Singleton viola el SRP (principio de responsabilidad única): la clase Singleton, además de sus responsabilidades inmediatas, también controla el número de sus copias.
-
La dependencia de una clase o método regular de un singleton no es visible en el contrato público de la clase.
-
Las variables globales son malas. El singleton eventualmente se convierte en una variable global importante.
-
La presencia de un singleton reduce la capacidad de prueba de la aplicación en general y de las clases que utilizan el singleton en particular.
GO TO FULL VERSION