Aplicaciones comunes del patrón Singleton
- Gestores de recursos (como conexiones a bases de datos)
- Administradores de configuración o propiedades
El patrón Singleton se utiliza principalmente para compartir instancias únicas, reduciendo el uso innecesario de memoria y mejorando la eficiencia del sistema. ### Forma hambrienta (Eager Initialization) – Segura por JVM
Esta versión garantiza que la instacnia se cree durante la carga de la clase. Debido a que JVM solo carga cada clase una vez, el uso de static asegura que el objeto se inicialice solo una vez.
package com.example.design.singleton;
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
public static EagerSingleton getInstance() {
return INSTANCE;
}
public void execute() {
System.out.println("Operación ejecutada");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(getInstance().hashCode());
}).start();
}
}
}
Forma perezosa con sincronización (Lazy Initialization + Synchronization)
Se implementa bajo control de hilos, pero el bloqueo sobre el método reduce el rendimiento. La solución más eficiente es usar un bloque sincronizado dentro del método, junto con verificación doble para evitar múltiples instancias.
El uso de volatile previene reordenamientos del compilador que podrían causar instancias nulas.
package com.example.design.singleton;
public class LazySynchronizedSingleton {
private static volatile LazySynchronizedSingleton instance;
public static LazySynchronizedSingleton getInstance() {
if (instance == null) {
try {
Thread.sleep(500); // Simular retardo
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
synchronized (LazySynchronizedSingleton.class) {
if (instance == null) {
instance = new LazySynchronizedSingleton();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(LazySynchronizedSingleton.getInstance().hashCode());
}).start();
}
}
}
Clase interna estática (Best Prcatice) – Carga diferida + Seguridad por JVM
La clase interna no se carga hasta que se accede al método getInstance(), lo cual permite una carga diferida. Además, el cargador de clases de JVM garantiza que solo se cree una instancia.
package com.example.design.singleton;
public class StaticInnerClassSingleton {
private static class Holder {
static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(StaticInnerClassSingleton.getInstance().hashCode());
}).start();
}
}
}
Singleton basado en enum
La forma más robusta y segura. El lenguaje Java garantiza que los enums tengan solo una instancia. Además, evita ataques mediante reflexión o deserialización.
Los enums no tienen constructores accesibles externamente, por lo que no pueden ser recreados incluso si se intenta serializar/deserializar.
package com.example.design.singleton;
public enum EnumSingleton {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(EnumSingleton.INSTANCE.hashCode());
}).start();
}
}
}