Patrón Singleton en Java: Múltiples Implementaciones

El patrón Singleton en Java se puede implementar de diversas maneras, cada una con características específicas en términos de seguridad de hilos, eficiencia y carga diferida. A continuación, se describen cinco enfoques comunes, con ejemplos de código modificados para reducir la similitud con versiones típicas.

1. Carga Anticipada (Eager Loading)

Esta implementación es senclila y segura para hilos, ya que la instancia se crea al cargar la clase. Sin embargo, no permite carga diferida, lo que puede causar desperdicio de recursos si el objeto no se utiliza.

public class CargaAnticipada {
    private static final CargaAnticipada instanciaUnica = new CargaAnticipada();
    
    private CargaAnticipada() {
        // Constructor privado para prevenir instanciación externa
    }
    
    public static CargaAnticipada obtenerInstancia() {
        return instanciaUnica;
    }
}

2. Carga Perezosa con Sincronización (Lazy Loading)

Este método permite la carga diferida al crear la instancia solo cuando se necesita. Utiliza sincronización para garantizar la seguridad en entornos multihilo, aunque puede reducir el rendimiento debido al bloqueo.

public class CargaPerezosa {
    private static CargaPerezosa instanciaUnica;
    
    private CargaPerezosa() {
        // Constructor privado
    }
    
    public static synchronized CargaPerezosa obtenerInstancia() {
        if (instanciaUnica == null) {
            instanciaUnica = new CargaPerezosa();
        }
        return instanciaUnica;
    }
}

3. Doble Verificación con Bloqueo (DCL)

El enfoque de Doble Verificación busca optimizar el rendimiento al minimizar la sincronización. Utiliza la palabra clave volatile para evitar problemas de visibilidad en la memoria compartida. Nota: debido a complejidades en el modelo de memoria de la JVM, esta técnica debe usarse con precaución.

public class SingletonDobleVerificacion {
    private volatile static SingletonDobleVerificacion instancia;
    
    private SingletonDobleVerificacion() {
        // Constructor privado
    }
    
    public static SingletonDobleVerificacion obtenerInstancia() {
        if (instancia == null) {
            synchronized (SingletonDobleVerificacion.class) {
                if (instancia == null) {
                    instancia = new SingletonDobleVerificacion();
                }
            }
        }
        return instancia;
    }
}

4. Clase Interna Estática

Esta implementación combina seguridad de hilos y carga diferida de manera eficiente. La clase interna estática se carga solo cuando se envoca el método obtenerInstancia(), garantizando una inicailización segura sin sincronización explícita.

public class SingletonInterno {
    private static class ClaseInterna {
        private static final SingletonInterno INSTANCIA = new SingletonInterno();
    }
    
    private SingletonInterno() {
        // Constructor privado
    }
    
    public static SingletonInterno obtenerInstancia() {
        return ClaseInterna.INSTANCIA;
    }
}

5. Enumeración (Enum)

Usar una enumeración es la forma más concisa y robusta en Java. Las enumeraciones son inherentemente singleton, seguras para hilos y protegidas contra reflexión y deserialización, aunque no permiten carga diferida.

public enum SingletonEnum {
    INSTANCIA;
    
    public void realizarAccion() {
        // Lógica de negocio aquí
    }
}

Para utilizar este singleton, simplemente se llama a través de la enumeración:

public class Principal {
    public static void main(String[] args) {
        SingletonEnum.INSTANCIA.realizarAccion();
    }
}

Consideraciones para la Selección

  • Si el objeto singleton consume pocos recursos y no se necesita carga diferida, la enumeración es preferible sobre la carga anticipada.
  • Si el objeto consume muchos recursos y requiere carga diferida, la clase interna estática es superior a la carga perezosa con sincronización.

Etiquetas: java singleton-pattern design-patterns thread-safety enumeration

Publicado el 6-19 04:54