Esperando por datos

De ChuWiki


Sincronizar hilos[editar]

En nuestros programas java puede pasarnos que necesitemos que un hilo espere que otro hilo termine. Qizás porque necesitamos que nos de unos datos, que termine de hacer unas cuentas, etc, etc. Mientras espera, lo deseable es que nuestro hilo esté dormido y no consuma recursos y que se despierte (o alguien le despierte) cuando esos datos lleguen o estén disponibles.

Podemos hacer esto con los métodos wait() y notify() de los que disponen todos los objetos java, todas las clases que heredan de Object.

wait() y notify()[editar]

Cuando llamamos al método wait() de un objeto, nuestro hilo se queda dormido hasta que otro hilo llame al método notify() de dicho objeto. Por ejemplo, si hacemos

 // Aquí nos quedamos bloqueados hasta que alguien llame notify()
 objeto.wait();

hasta que en otro hilo no se haga

 // Esto desbloquea al hilo anterior
 objeto.notify();

Para poder hacer estas llamadas, el hilo debe ser el "propietario del monitor". Esto quiere decir que debemos hacer estas llamadas en un synchronized que afecte al objeto, de esta manera

 synchronized (objeto)
 {
    // esperando datos disponibles
    objeto.wait();
 }

y de esta otra para el "avisador"

 synchronized (objeto)
 {
    // Han llegado los datos, se lanza el aviso.
    objeto.notify();
 }

Si no lo hacemos así, obtendremos una excepción java.lang.IllegalMonitorStateException: current thread is not owner.

Encima, también es necesario meter esto en un bloque try-catch, puesto que estas llamadas puede lanzar una excepción. El código debería ser por tanto, como esto

synchronized(objeto)
{
    try
    {
        // Esperando
	objetos.wait();
    }
    catch(Exception e)
    {
	e.printStackTrace();
    }
}

o como esto

synchronized(objeto)
{
    try
    {
        // Se lanza el aviso
	objetos.notify();
    }
    catch(Exception e)
    {
	e.printStackTrace();
    }
}


Métodos synchronized[editar]

Una forma de evitar tener que escribir este código cada vez, es aprovechar un objeto que hagamos al efecto, en el que se guarden los datos y que tenga dos métodos: dameDatos() y tomaDatos(). El primero se quedará bloqueado hasta que lleguen los datos. El segundo guardará los datos recibidos y despertará al hilo dormido. Esta clase hecha al efecto puede ser así

public class Datos
{
   Object losDatos = null;

   /** Esta llamada se queda bloqueada hasta que haya
    *  datos disponibles */
   synchronized public Object dameDatos()
   {
      if (losDatos==null)
      {
         try
         {
            // bloqueo hasta que lleguen datos
            wait();
         }
         catch(Exception e)
         {
            e.printStackTrace();
         }
      }
      return losDatos;
   }

   /** Guarda los datos y avisa a los durmientes */
   synchronized public void tomaDatos (Object nuevosDatos)
   {
      losDatos=nuevosDatos;
      try
      {
         // Se avisa a los durmientes
         notify();
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
   }
}

Todo esto es equivalente a lo que comentamos anteriormente. Al poner synchronized en los métodos es equivalente a hacer un synchronized(this) dentro del código del método.

Cuando alguien llama a objeto.dameDatos() se quedará bloqueado y durmiendo hasta que alguien llame a tomaDatos().

Cuando alguien llame a objeto.tomaDatos(), se guardarán los datos y se despertará a los que están a la espera.

Por supuesto, el código de este ejemplo tiene muchos fallos. Habría que poner protecciones o al menos contemplar casos como llamar a tomaDatos() pasando un null, ver qué pasa si se llama varias veces seguidas al tomaDatos() y nadie los recibe, etc, etc.