January 28, 2026 (2mo ago)

Guía del patrón Singleton en Java para código limpio

Domina el patrón singleton en Java con nuestra guía sobre seguridad de hilos, pruebas y alternativas modernas como la inyección de dependencias para lograr código mantenible y escalable.

← Back to blog
Cover Image for Guía del patrón Singleton en Java para código limpio

Domina el patrón singleton en Java con nuestra guía sobre seguridad de hilos, pruebas y alternativas modernas como la inyección de dependencias para lograr código mantenible y escalable.

Guía del patrón Singleton en Java para código limpio

Resumen: Domina el patrón singleton en Java con nuestra guía sobre seguridad de hilos, pruebas y alternativas modernas como la inyección de dependencias para lograr código mantenible y escalable.

Introducción

El patrón Singleton en Java garantiza que una clase tenga una única instancia y proporciona un único punto de acceso global a la misma. Esto es útil para objetos como gestores de configuración, pools de conexiones o servicios centrales de registro (logging) donde múltiples instancias provocarían un estado inconsistente o desperdicio de recursos. Entender cómo implementar un Singleton seguro y probables —y cuándo evitarlo— es esencial para un código Java limpio y mantenible.

Entendiendo el patrón de diseño Singleton

Una torre de control de tráfico aéreo rodeada de aviones coloridos en un patrón circular, ilustrando la gestión del tráfico aéreo.

Una analogía útil es la torre de control de un aeropuerto. No construyes una torre separada para cada avión; cada avión se comunica con la misma torre. La torre es la única fuente de verdad. Ese es el papel que desempeña un Singleton en una aplicación.

El patrón se popularizó en la literatura clásica de patrones de diseño y en sistemas Java empresariales1. Sus responsabilidades principales son simples:

  • Garantizar una única instancia — típicamente haciendo el constructor privado.
  • Proporcionar un punto de acceso global — normalmente un método estático como getInstance().

Características clave

  • Una sola instancia: La clase impide la creación de más de una instancia.
  • Constructor privado: Impide la instanciación directa por otras clases.
  • Punto de acceso global: Un método estático devuelve la instancia única.
  • Ciclo de vida autosuficiente: La clase administra su propia instancia.

Conclusión clave: El Singleton impone un único objeto y un acceso global controlado para que diferentes partes de una aplicación hablen con exactamente la misma instancia.

Implementando Singletons seguros para hilos en Java

Una ilustración de una caja fuerte con un candado, recibiendo múltiples flechas coloridas, simbolizando almacenamiento seguro.

Un Singleton perezoso (lazy) ingenuo es simple pero no es seguro para hilos. Considera este ejemplo básico:

public class BasicLazySingleton {
    private static BasicLazySingleton instance;

    private BasicLazySingleton() {}

    public static BasicLazySingleton getInstance() {
        if (instance == null) {
            instance = new BasicLazySingleton();
        }
        return instance;
    }
}

En un entorno multihilo, dos hilos pueden observar instance == null y ambos crear una nueva instancia. Una solución directa es sincronizar getInstance(), pero eso causa bloqueo innecesario en cada llamada.

Método sincronizado (funciona, pero costoso)

public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;

    private SynchronizedSingleton() {}

    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

Esto resuelve la seguridad de hilos pero perjudica el rendimiento porque la sincronización se ejecuta en cada acceso.

Bill Pugh (Initialization-on-demand Holder)

Un enfoque más limpio es el idiom Initialization-on-demand Holder. Proporciona inicialización perezosa y seguridad de hilos sin la sobrecarga de sincronización porque la inicialización de clases es segura para hilos en la JVM2.

public class BillPughSingleton {
    private BillPughSingleton() {}

    private static class SingletonHolder {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Esto se apoya en la JVM para cargar SingletonHolder solo cuando se llama a getInstance(), y las garantías de inicialización de clases proporcionan la seguridad de hilos.

Singletons con enum (recomendado)

Joshua Bloch recomienda usar un enum de un solo elemento para un Singleton. Esto es conciso y protege contra ataques por reflexión y serialización3.

public enum EnumSingleton {
    INSTANCE;

    public void someMethod() {
        // business logic
    }
}

Beneficios:

  • Código mínimo
  • Seguridad de hilos proporcionada por la JVM
  • Seguridad en la serialización
  • Fuerte protección contra la creación de instancias basada en reflexión

En la mayoría de los casos, un Singleton con enum es la opción más robusta y mantenible.

Los costos ocultos del patrón Singleton

Un boceto surrealista de una habitación con dispositivos electrónicos en las paredes, conectados por cables coloridos a numerosos insectos.

Aunque los Singletons resuelven un problema particular, introducen costos ocultos: acoplamiento fuerte, estado global y reducción de la capacidad de prueba.

Cuando el código llama a Singleton.getInstance(), esa dependencia está oculta. El contrato público de la clase no revela que depende de un objeto global. Esto conduce a:

  • Código rígido que es difícil de cambiar
  • Pruebas que son frágiles o requieren la instancia global real
  • Dificultad para ejecutar pruebas en paralelo debido al estado compartido

Problemas de pruebas

Los Singletons dificultan las pruebas unitarias aisladas. No puedes intercambiar fácilmente un mock, por lo que las pruebas a menudo usan la implementación real. Eso puede causar pruebas lentas, acoplamiento accidental a sistemas externos y pipelines de CI frágiles.

Una clase que depende de un Singleton oculta esa dependencia en su firma, lo que hace que el código sea más difícil de razonar y mantener.

Estado global y dependencias ocultas

Un Singleton es esencialmente una variable global. El estado global oscurece el flujo de información y crea interdependencias difíciles de desenmarañar. Esto complica la depuración y ralentiza el desarrollo.

Para más información sobre anti-patrones comunes y mantenibilidad, consulta nuestra guía sobre patrones de diseño en OOP y estrategias de pruebas.

Alternativas modernas a los Singletons

A medida que los sistemas evolucionaron, los desarrolladores adoptaron patrones que evitan las desventajas del Singleton a la vez que preservan la creación controlada de objetos.

Inyección de dependencias

La Inyección de Dependencias (DI) invierte la responsabilidad: los clientes declaran sus dependencias y un contenedor externo se las proporciona. Esto hace las dependencias explícitas y fáciles de reemplazar en pruebas. Frameworks de DI como Spring y Guice gestionan los ciclos de vida de los objetos y el ensamblado por ti4.

Beneficios de DI:

  • Desacoplamiento — los componentes dependen de abstracciones, no de clases concretas
  • Testabilidad — puedes inyectar mocks o fakes
  • Flexibilidad — intercambia implementaciones mediante configuración

Esto se alinea con las mejores prácticas de inversión de control y produce sistemas más claros y mantenibles7.

Factorías

El patrón Factory centraliza la lógica de creación. Una factoría puede devolver la misma instancia o nuevas instancias según se requiera. El código cliente pide el objeto a la factoría sin saber cómo se crea, lo que mantiene tu código modular y testeable.

Instancias con alcance (Scoped Instances)

A veces necesitas una sola instancia, pero solo dentro de un alcance limitado (request, session o aplicación). Los frameworks soportan beans con alcance por request o por sesión para proporcionar un equilibrio entre reutilización de recursos e aislamiento.

Singletons en sistemas distribuidos

Un Singleton local a la JVM no te da una única instancia a través de múltiples instancias de servicio. Los microservicios ejecutan múltiples JVMs, por lo que necesitas soluciones distribuidas para estado compartido, como Redis o un servicio de configuración centralizado como Consul o Spring Cloud Config6.

Cómo refactorizar Singletons en código legado

Un hombre desmonta un monolito complejo y pesado en muchos módulos más pequeños y coloridos para un contenedor.

Refactorizar Singletons requiere cuidado. El problema central es la dependencia directa en la llamada estática getInstance(). Un enfoque gradual y metódico reduce el riesgo.

Estrategia paso a paso:

  1. Introduce una interfaz que describa los métodos públicos del Singleton, y haz que el Singleton la implemente.
  2. Haz las dependencias explícitas añadiendo parámetros de constructor en las clases que usan el Singleton.
  3. Usa una factoría o un contenedor DI (Spring, Guice) para proporcionar la implementación como una instancia gestionada.
  4. Reemplaza las llamadas a Singleton.getInstance() por dependencias inyectadas por constructor.
  5. Elimina el código específico del Singleton una vez que todos los llamadores reciban la dependencia externamente.

Beneficios: modularidad mejorada, testabilidad y claridad. Refactorizar Singletons transforma una base de código rígida en componentes flexibles y testeables.

Preguntas frecuentes

¿Es el patrón Singleton un anti-patrón?

A menudo, sí. El Singleton introduce estado global y acoplamiento fuerte, lo que reduce la testabilidad e incrementa el coste de mantenimiento a largo plazo. Úsalo con moderación y prefiere DI o instancias con alcance cuando sea factible.

¿Cómo puede romperse un Singleton en Java?

La reflexión y la serialización pueden crear nuevas instancias a menos que te protejas explícitamente contra ellas. Usar un enum para un singleton evita estas trampas3.

¿Es apropiado un Singleton para microservicios?

No. Un Singleton está limitado a la JVM, por lo que cada instancia del servicio tendrá su propio Singleton. Para estado compartido entre servicios, usa sistemas distribuidos como Redis o servicios de configuración centralizados6.

Tres resúmenes concisos de preguntas y respuestas

P: ¿Cuándo debería usar un Singleton? R: Solo cuando una única instancia represente realmente un recurso único y global dentro de una sola JVM y no exista una alternativa mejor. Prefiere Singletons con enum si debes usar uno.

P: ¿Cómo hago un Singleton perezoso y seguro para hilos? R: Usa el idiom Initialization-on-demand Holder (Bill Pugh) o un enum. Ambos proporcionan seguridad de hilos con una sobrecarga mínima; el enum además protege contra problemas de serialización y reflexión23.

P: ¿Qué debo usar en lugar de Singletons para mejorar la testabilidad? R: Usa Inyección de Dependencias, factorías o instancias con alcance para que las dependencias sean explícitas y fácilmente reemplazables en pruebas47.


1.
Design Patterns: Elements of Reusable Object-Oriented Software (el “Gang of Four”), Erich Gamma et al., 1994. https://en.wikipedia.org/wiki/Design_Patterns
2.
Java Language Specification, sección sobre inicialización de clases e interfaces; la inicialización de clases se realiza en el primer uso activo y es segura para hilos. https://docs.oracle.com/javase/specs/
3.
Joshua Bloch, Effective Java — recomienda singletons con enum para seguridad frente a serialización y reflexión. https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000006973/9780134685991
4.
Documentación del framework Spring sobre Inyección de Dependencias y scopes de beans. https://spring.io/projects/spring-framework
5.
Los problemas con serialización y reflexión se tratan en Effective Java y en la documentación de serialización de Java. https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
6.
Patrones de microservicios y orientación sobre estado distribuido; los singletons de una sola JVM no proporcionan semántica de singleton entre servicios. Ver Microservices.io y recursos relacionados. https://microservices.io/
7.
Martin Fowler sobre principios de Inversión de Control e Inyección de Dependencias. https://martinfowler.com/articles/injection.html
← Back to blog
🙋🏻‍♂️

La IA escribe código.
Tú lo haces durar.

En la era de la aceleración de la IA, el código limpio no es solo una buena práctica — es la diferencia entre sistemas que escalan y bases de código que colapsan bajo su propio peso.