Blog de Cymo - un poquito abandonado

miércoles, 23 de abril de 2008

Java Singleton Multithread Safe (Patrón Singleton en Java y seguro multihilo)

No me refiero a un solterón al que le acosen por varias vías, si no a un problema que me he encontrado hoy, programando en Java (java doo).

Tenía una clase que quería que fuese Singleton, con lo que he hecho las magias de siempre, y me sale algo como esto:

public class DrCymo {
private static DrCymo instancia = null;


private
DrCymo() {} // constructor privado


public static DrCymo getInstancia() {
if (instancia == null) instancia = new DrCymo(); // solo se instancia una vez
return instancia;
}

}



Osea, el viejo truco de poner el constructor privado para que nadie pueda instanciar la clase, y permitiendo el acceso desde el método getInstancia(); y haciendo "instanciación diferida": únicamente se crea el objeto la primera vez que se llama a getInstancia();


Pero, ¿qué pasa si casualidades de la vida, varios hilos a la vez llaman al método getInstancia() ?
En determinadas circustancias ( en que realmente SOLO queremos una instancia) se puede liar una pajarraca si poliinstanciamos, especialmente si (en otro lenguaje que no sea Java) decidimos destruir la única instancia que tenemos deberíamos tener.

Una forma de solucionarlo sería eliminar la instanciación diferida:
private static DrCymo instancia = new DrCymo();


Ahora la instancia no se crea en el constructor sino al cargar la clase. Pero claro, con eso perdemos los beneficios de la instanciación postergada. ¿Se puede hacer algo?

Mi solución:


public class DrCymo {
private static DrCymo instancia = null;


private
DrCymo() throws DemasiadosHilosException;
{
synchronized (DrCymo.getClass())
{
if (instancia != null)
throw new DemasiadosHilosException();
else {
instancia = this;
}
}
} // constructor privado


public static DrCymo getInstancia() {
if (instancia == null)
try { new DrCymo(); }
catch (DemasiadosHilosException demasiadaGente) {}
return instancia;
}

}


Explicación: en el constructor, mediante el hechizo del synchonized hacemos uso de un monitor sobre el objeto meta-clase DrCymo, como podríamos haber usado cualquier otro.
El monitor (rentrante) impide que haya más de un hilo a la vez, ejecutando el código synchronized que es lo que queremos.
Lo he puesto en el constructor y no en getInstancia() porque todo código synchronized tiene una penalización de ejecución y lo lógico es que, en casos extremos, sólo unos pocos hilos lleguen realmente a llamar al constructor. Cualquier hilo, que llegue a entrar en el constructor, sin ser el primero, lanzará una excepción que abortará la creación del objeto. Posteriormente, todos harán uso de getInstancia() que no es sincronizado (¡más rápido!) pero que una vez inicializada la instancia, ya no hay problemas de hilos.

La solución, sin embargo, no es del todo buena: si dos hilos llaman a la vez a getInstancia(), uno de los hilos construirá el objeto y el otro no, gracias a la excepción que se lanza, como ya he dicho. Por tanto, puede ocurrir que algún hilo llame a getInstancia() y se le devuelva null.

Como decían en los anuncios de cierto tipo de juguetes: "Próximo episodio en tu casa". La solución es relativamente "sencilla", aunque no la voy a colocar todavía para ver si alguien lee mi blog ;-) (y de paso, para retar a los geroflifigallos).

De todos modos, por muy sencilla que sea la solución, tiene varias pegas:

  • Requiere usar algún objeto más.
  • Se puede hacer en plan eleguante, con Java 5, o bien al estilo de Java 2.
  • Puede que la solución global, si bien algo más eficiente de cara a la ejecución, resulte demasiado "compleja" de entender, pues hay otras aproximaciones, también seguras más sencillas de implementar. En otras palabras, en la mayoría de casos, no compensaría el beneficio al esfuerzo realizado en escribir tanta línea.
Y varias ventaja:
  • Invita a reflexionar sobre el tema
  • Me permite cambiar la temática de los posts
  • Me permite postear algo más antes de irme a dormir... zzzZZZ
  • Abre la puerta a que otro día hable del synchronized en más detalle, de las utilidades de concurrencia de Java 5 y de los ThreadPools y otras aberraciones.