Monday, May 20, 2013

Concurrencia en Java Parte 4

Esta es la cuarta parte del tutorial, este link te lleva al  post anterior concurrencia en java parte-3


Thread Deadlock

Deadlock sucede cuando dos threads se bloquean en espera del lock de un objeto que cada thread posee. Ni uno de los threads se puede ejecutarse hasta que el otro abandone su lock, por lo que esperarán para siempre o se bloqueara indefinidamente.

Ejemplo:

 public class Deadlock extends Thread {  
           Recurso a;  
           Recurso b;  
           public Deadlock(Recurso a, Recurso b,String nombre) {  
                super(nombre);  
                this.a = a;  
                this.b = b;  
           }  
           public void run(){  
                synchronized(a) {  
                       try {                           
                          Thread.sleep(10000);  
                     } catch (InterruptedException e) {  
                          e.printStackTrace();  
                     }  
                     synchronized(b) {  
                     }  
                     System.out.println("Thread " + this.getName() + " ha terminado");       
                }  
           }  
      }  
      ..........  
      Recurso a = new Recurso();  
      Recurso b = new Recurso();  
      Deadlock d1 = new Deadlock(a, b, "uno");  
      Deadlock d2 = new Deadlock(b, a, "dos");  
      d1.start();  
      d2.start();  
      ..........  

En el ejemplo anterior ningún mensaje se imprime en consola porque los threads nunca llegan a la sentencia System.out.println(), ambos threads quedan para siempre esperando en la segunda sentencia synchronized().

Lo que sucede es que el primer thread "d1" adquiere el lock del recurso "a" y el segundo thread "d2" adquiere el lock del recurso "b", entonces los dos threads duermen durante 10 segundos, el tiempo de que duermen es para dar tiempo a cada thread de ejecutarse y adquirir el lock, luego después de despertarse el deadlock sucede, cada thread tratará de obtener el recurso que el otros posee, "d1" tratara de adquirir "b" y "d2" tratará de adquirir "a".

Una forma de evitar los deadlocks es utilizar la interfaz Lock y obtener el lock de los objetos con el método tryLock() .

Interacción de Threads

Temas pueden comunicar a otros threads el estado de un evento. La API de Java proporciona dos formas de habilitar esta comunicación.
  1. Los métodos de ayuda de la clase Object (wait(), notify(), notifyAll()).
  2. La interfaz java.util.concurrent.locks.Condition.
Estos mecanismos permiten a un thread ponerse en un estado de espera hasta que otro thread lo despierte si hay una razón para volver del estado de espera.

Un thread tiene que llamar a los metodos wait(), notify(), notifyAll() de un objeto desde un contexto sincronizado o de lo contrario una IllegalMonitorStateException se lanzara. El thread debe sincronizar la instancia del objeto en el que está llamando a los métodos de ayuda.

Ejemplo:

 public class Calculador extends Thread {  
       private Operacion op;  
       public void run() {  
          synchronize(op) {  
             op.wait();  
          }  
       }  
 }  


Existe una versión sobrecargada del método wait() que espera una cantidad de tiempo, por lo que el thread espera hasta que sea notificado o transcurra el tiempo especificado.

Cuando se invoca el método wait() en un objeto, el thread que ejecucta ese código libera el lock del objeto, sin embargo, cuando notify() o notifyAll() son invocados en un objeto el thread no renuncia al lock, el lock se libera hasta que el código sincronizado donde el notify() o notifyAll() se utilizan es completado.

Un ejemplo común de interacción de threads es el consumidor-productor, en este tipo de programa de dos threads interactúan enviando mensajes entre ellos a través de un "monitor". El thread productor como su nombre lo dice genera mensajes para el consumidor, por otro lado el thread consumidor espera a los mensajes generado por el productor.

Ejemplo:

 public class Correo {  
      private String mensaje;  
      private boolean vacio = true;  
      public synchronized String recibir() {  
           //Espera hasta que el mensaje este disponible  
           while(empty){  
                try {  
                     wait();  
                } catch (InterruptedException e) {  
                     e.printStackTrace();  
                }                 
           }  
           vacio=true;  
           notifyAll();  
           return mensaje;  
      }  
      public synchronized void enviar(String mensaje){  
           //Espera hasta que el mensaje sea leido  
           while(!vacio) {  
                try {  
                     wait();  
                } catch (InterruptedException e) {  
                     e.printStackTrace();  
                }  
           }  
           vacio = false;  
           this.mensaje = mensaje;  
           notifyAll();  
      }  
 }  
 public class Productor extends Thread {  
      private Correo correo;  
      public Producer(Correo correo) {  
           this.correo = correo;  
      }  
      public void run() {  
           String [] mensajes = {"Hola", "mundo", "fin"};  
           for (String men:mensajes) {  
                correo.enviar(men);  
           }  
      }       
 }  
 public class Consumidor extends Thread {  
      private Correo correo;  
      public Consumer( Correo correo) {  
           this. correo = correo;  
      }  
      public void run() {  
           String mensaje = "";  
           while (!mensaje.equalsIgnoreCase("fin")) {  
                mensaje = correo.recibir();  
                System.out.print(mensaje + " ");  
           }  
      }  
 }  
 ..............  
 Correo correo = new Correo();   
 Consumidor consumidor = new Consumidor(correo);  
 Productor productor = new Productor(correo);  
 consumidor.start();  
 productor.start();  
 ..............  

En el ejemplo de la clase de Correo actúa como un monitor, si no sabes lo que un monitor ver este link http://en.wikipedia.org/wiki/Monitor_(synchronization), esta clase hace que el consumidor deba esperar si hay ningún mensaje para leer y le notifica cuando llega un mensaje. Es lo mismo para el productor, lo hace esperar si el productor quiere enviar un mensaje y el último mensaje que se mando no se ha leído y le notifica cuando el mensaje se ha leído.
Por lo tanto, la clase Correo a través de sus métodos wait(), notify(), notifyAll() hace más fácil la interacción entre los threads consumidores y productores, es importante tener en cuenta que estos métodos se utilizan dentro de ciclos de condición, de esta manera es más seguro de usar este métodos y evitar situaciones de tipo deadlock.

La interfaz java.util.concurrent.locks.Condition proporciona una funcionalidad similar a los métodos de ayuda de la clase Object.
Las condiciones se asocian con locks, las condiciones permiten a los threads tener el control de un lock y comprobar si una condición es verdadera o no, si es falsa, se suspende el thread hasta que otro thread los despierte.

El API de Java dice lo siguiente acerca de la interfaz Condition:
La interfaz Condition provee métodos como los métodos monitor de la clase Object (wait, notify y notifyAll). Cuando un Lock reemplaza el uso de los métodos y declaraciones sincronizados, una condición reemplaza el uso de los métodos objeto de monitor.

Todas las condiciones están asociadas a un lock y para obtener una instancia de una condición para un lock específica, se usa el método newCondition().

                               Lock lock = new ReentrantLock();
                               Condition condicion = lock.newCondition();

La interfaz Condition tiene los métodos await(), signal(), signalAll() y su comportamiento es igual que los métodos wait(), notify(), notifyAll() de la clase Object. Estos métodos deben estar en un bloque de código que comienza con una llamada al método lock() de un objeto de tipo Lock y termina con un método de unlock() en el mismo objeto, o una excepción IllegalMonitorStateException se lanzara.

 
                                          
            lock.lock();//Obtiene el lock  
               try {  
                   condicion.await();             
            } catch (InterruptedException e) {  
                 e.printStackTrace();  
            } finally {  
                 lock.unlock();//Libera el lock  
               }  

Para implementar el ejemplo de consumidor-productor con condiciones, sólo tenemos que cambiar la clase de Correo la que actúa como monitor, hay que retirar las palabras clave synchronized por un lock con una condición, y cambiar los métodos de ayuda de la clase Object por los de la condición.

 public class Correo {  
      private String mensaje;  
      private boolean vacio = true;  
      private Lock lock = new ReentrantLock();  
      private Condition condicion = lock.newCondition();  
      public String recibir() {  
           //Espera hasta que el mensaje este disponible  
           lock.lock();  
           while(vacio){                 
                try {  
                     condicion.await();  
                } catch (InterruptedException e) {  
                     e.printStackTrace();  
                }                           
           }  
           vacio=true;  
           condicion.signalAll();  
           lock.unlock();  
           return mensaje;  
      }  

      public void enviar(String mensaje){  
           //Espera hasta que el mensaje sea leido  
           lock.lock();  
           while(!vacio) {  
                try {  
                     condicion.await();  
                } catch (InterruptedException e) {  
                     e.printStackTrace();  
                }                 
           }  
           vacio = false;  
           this. mensaje = mensaje;  
           condicion.signalAll();  
           lock.unlock();  
      }  
 }  


En el ejemplo anterior, el método de la await() de la condición actúa como el método wait() de la clase Object y signalAll() actúa como notify(), y el comportamiento debe ser el mismo.

Ir a parte 5

No comments:

Post a Comment