Thursday, May 19, 2011

Singletons en Java

Tras realizar un diseño de un servicio, me puse a definir las clases necesarias, con su esqueleto básico con lo que varias requerían ser Singletons, por lo que utilicé la implementación típica sacada de los libros de ingeniería de software.

public class DefaultSingleton {
private static DefaultSingleton instance = null;

private DefaultSingleton(){}

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

En teoría esto permite la inicialización única para accesos concurrentes ya que el getInstance() es sincronizado, pero cualquier objeto que necesite obtener la instancia deberá pasar por la sincronización, lo que puede no ser necesario y si no me equivoco también dará problemas de concurrencia.

Para intentar evitar esta situación, se ha confiado durante mucho tiempo en el doble chequeo de la sincronización (Double-Checked Locking), pero sin entrar en más detalle, decir que se ha demostrado que genera diversos errores en entornos altamente concurrentes, sin entrar en más detalles pongo un enlace.
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

A partir de aquí surgen dos alternativas, la inicialización impaciente (eager) o la perezosa (lazy). Los dos ejemplos anteriores son Lazy. La principal diferencia entre una y otra es cuándo se crea la instancia, si al iniciar la aplicación o al hacer el getInstance(). La elección de cada una dependerá de cada caso concreto, ya que si tienes pocos Singleton y requieren poco procesamiento, o simplemente te da igual lo que tarde en iniciarse la aplicación, optarás por la primera, si son muchos los Singleton que necesitas inicializar, algunos contienen un procesamiento complejo y/o lento, o simplemente prefieres que se vayan creando las instancias conforme vayan haciendo falta, por temas de recursos o lo que sea, tal vez prefieras la segunda opción. En mi caso me he decantado por la inicialización bajo demanda.

Ejemplo de inicialización impaciente.
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();

private EagerSingleton(){}

public EagerSingleton getInstance() {
return instance;
}

Se puede observar como evitamos el synchronized ya que es la JVM quien se encarga de inicializar la instancia. Al centralizar la inicialización en un único punto (la JVM) previenes cualquier inicialización concurrente.

Ejemplo de inicialización perezosa (o bajo demanda).
public class LazySingleton {
private static class InstanceHolder {
public static LazySingleton instance = new LazySingleton ();
}

private LazySingleton (){}

public LazySingleton getInstance() {
return InstanceHolder.instance;
}
Aquí no se instanciará la variable instance hasta que algún objeto haga el getInstance() de tal forma que igual que en el caso anterior, será la JVM quien creará la instancia cuando deba ser ejecutado.

Espero que este resumen rápido sirva para tener claro cómo utilizar un Singleton en Java.

Aquí dejo un par de fuentes
http://embarcaderos.net/2009/06/23/the-singleton-pattern-in-java-multi-threaded-applications/
http://www.rockstarprogrammer.org/post/2006/oct/19/java-singletons-without-locks/