Hay muchas situaciones donde necesitas crear un tipo especial de clases. Una de las cuales sigue el patrón singleton.

Hay escenarios en los cuales quieres asegurarte que una única instancia está presente para determinada clase. Por ejemplo, vamos a suponer que defines una clase que modifica un registro, o una clase que administra el spooling(agregar documentos al buffer de impresión) de una impresora, o incluso creaste una clase que maneja un pool de hilos (thread-pool). En todos esos casos tu quieres evitar el difícil trabajo de encontrar bugs de instanciación, el cual ocurre al asegurarse que solo exista un objeto de tales clases. Para esas situaciones puedes crear un clase Singleton.Una clase singleton asegura que solo una instancia de esa clase es creada. Veamos un ejemplo.

Imaginemos que quieres implementar una clase para logging de una aplicación, con la cual seguiremos los detalles de la ejecución para depurar (debug). Para este objetivo tu debes asegurar que sólo una instancia de la clase Logger existe para toda la aplicación. Por lo tanto debes convertir a Logger en una clase Singleton.

// Clase Logger debe ser instanciada solo una vez en la aplicacion; Esto garantiza que toda la aplicacion usa la misma instancia de Logger
public class Logger {
    // declarar el constructor como privado previene que sea instanciada fuera de la clase
    private Logger() {}
    // Por defualt esta propiedad es inicializada como null
    private static Logger myInstance;
    // El metodo estatico es usado para obtener la instancia de la clase Logger
    public static Logger getInstance() {
        if (myInstance == null) {
            myInstance = new Logger();
        }
        // retorna el mismo objeto referenciado cada vez que getInstance es llamado
        return myInstance;
    }
    // Creo que esto no necesita explicacion
    public void log(String s) {
        System.err.println(s);
    }
}

Analicemos el Singleton anterior. El constructor de la clase es declarado como privado, esto hace que no sea posible crear una nueva instancia de Logger usando el operador new. La única forma de obtener una instancia de la clase es llamar al método estático getInstance(). El método valida si un objeto Logger existe o no. Si no existe, crea uno nuevo y lo asigna a la propiedad estática myInstance. De esta manera, cuando llames al método getInstance() siempre devolvera el mismo objeto.

¿Cómo garantizar que tu Singleton sea realmente un Singleton?

Es realmente importante (y también un poco difícil) asegurar que la implementación de tu Singleton permita sólo una instancia de la clase. El siguiente ejemplo funciona solo si tu aplicación es un único hilo. En el caso de múltiples hilos, al tratar de obtener el objeto puede resultar en la creación de múltiples objetos, lo cual evidentemente quiere decir que fallamos en el propósito de implementar un Singleton.

public class Logger {
    private Logger() {
        // private constructor to prevent direct instantiation
    }
    private static Logger myInstance;
    public static synchronized Logger getInstance() {
        if (myInstance == null) myInstance = new Logger();
        return myInstance;
    }
    public void log(String s) {
        // log implementation
        System.err.println(s);
    }
}

Nótese que usamos el keyword synchronized en esta implementación, esto es un mecanismo de concurrencia de Java que permite un único hilo a la vez dentro del scope de sincronización. Aquí hicimos que todo el método se sincronice con la intención de que sea accesible por un único hilo a la vez. Pero hay un pequeño problema, un pobre performance. Quisimos hacer que el método se sincronizará solo la primera vez cuando sea llamada, pero al declarar todo el método como synchronized, para todas las subsecuentes llamadas se convierten en un cuello de botella.

Para resolver esto veamos otra implementación de la clase Logger que se basa en la “Inicialización bajo demanda del propietario” (initialization on demand holder). Este tipo de implementaciones usa clases anidadas (inner class), y explora el hecho de que las clases anidadas no son cargadas hasta que son referenciadas.

public class Logger {
    private Logger() {
        // private constructor
    }
    public static class LoggerHolder {
        public static Logger logger = new Logger();
    }
    public static Logger getInstance() {
        return LoggerHolder.logger;
    }
    public void log(String s) {
        // log implementation
        System.err.println(s);
    }
}

Si quieres que escriba un post acerca de las clases anidadas, házmelo saber en los comentarios.