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
- Los métodos de ayuda de la clase Object (wait(), notify(), notifyAll()).
- 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