You are on page 1of 33

Mdulo 151

Hilos
Objetivos
Al completar este mdulo, usted debe ser capaz de: Definir un hilo (thread) Crear threads separados en un programa de tecnologa Java, controlando en cdigo y los datos que son usados por ese hilo Conrolar la ejecucin de un hilo y escribir cdigo independiente de la plataforma con hilo Describir las dificultades que pueden surgir cuando la informacin es compartida por mltiples hilo Usar wait y notify para comunicarse entre hilos Usar synchronized para proteger de corrupcin a los datos. Este mdulo cubre multihilvanado (multithreading), el cual habilita a un programa para realizar tareas mltiples al mismo tiempo.

Java Programming Languaje SL-275-SE6 Student Guide. Traduccin: Rodolfo Ochoa Cetina, 1 Diciembre 2011

Relevancia
Discusin - La siguiente pregunta se relaciona con el material presentado en este mdulo: Cmo logra que sus programas realicen tareas mltiples de manera concurrente?

Threads
Una vista simple de una computadora es que tiene un CPU que realiza clculos, memoria que contiene el programa que ejecuta la CPU, y la memoria que tiene la informacin sobre la que el programa opera. En esta vista, hay slo un trabajo realizado. Una vista ms completa de la mayora de los sistemas de computadora modernos provee la posibilidad de realizar ms de un trabajo al mismo tiempo. Usted no necesita preocuparse por cmo lograr el desempeo de tareas mltiples, slo considerar las implicaciones de un punto de vista de programacin. Realizando ms de una tarea es similar a tener ms de una computadora. En este mdulo, un thread, o execution context, es considerado una encapsulacin de una CPU virtual con su propio cdigo e informacin de programa. La clase java.lang.Thread le permite crear y controlar threads.

Nota - Este mdulo usa el trmino Thread cuando se refiere a la clase java.lang.Thread y hilo cuando se refiere al contexto de ejecucin.

Un hilo, o contexto de ejecucin est compuesto por tres partes principales: Una CPU virtual El cdigo que ejecuta la CPU Los datos con la que trabaja el cdigo Un proceso es un programa en ejecucin. Uno o ms threads constituyen un proceso. Un hilo est compuesto por una CPU, cdigo e informacin como se ilustra en la figura 15-1.

Figura 15-1 Un Thread El cdigo puede ser compartido por varios threads, independientemente de los datos. Dos threads comparten el mismo cdigo cuando ejecutan cdigo desde instancias de la misma clase.

De igual forma, los datos pueden ser compartidos por varios threads, independientemente del cdigo. Dos threads comparten los mismos datos cuando comparten el acceso a un objeto en comn. En la programacin en Java, la CPU virtual es encapsulada en una instancia de la clase Thread. Cuando un thread es construido, el cdigo y los datos estn definidos en su contexto que es especificado por el objeto que pasa a su constructor.

Creando el Hilo
Esta seccin examina cmo usted puede crear un hilo, y cmo puede usar sus argumentos de constructor para proveer el cdigo y los datos para el hilo cuando ste corra. Un constructor de Thread toma un rgumento que es una instancia de Runnable. Una instancia de Runnable es hecha a partir de una clase que implemente la interfaz Runnable (esto es que provea un mtodo public void run()). Por ejemplo: 1 public class ThreadTester { 2 public static void main(String args[]) { 3 HelloRunner r = new HelloRunner(); 4 Thread t = new Thread(r); 5 t.start(); 6 } 7 } 8 9 class HelloRunner implements Runnable { 10 int i; 11 12 public void run() { 13 i = 0; 14 15 while (true) { 16 System.out.println(Hello + i++); 17 if ( i == 50 ) { 18 break; 19 } 20 } 21 } 22 }

Primero, el mtodo main construye una instancia r de la clase HelloRunner. La instancia r tiene sus propios datos, en este caso el entero i. El entero i de r es el dato con la cual el hilo trabaja cuando ste corre porque la instancia, r, es pasada al constructor de la clase Thread. El hilo siempre comienza su ejecucin en el mtodo run de su instancia cargada Runnable (r en este ejemplo). Un ambiente de programacin multihilvanado le permite crear varios threads basados en la misma instancia Runnable. Usted puede hacer esto de la siguiente manera: Thread t1 = new Thread(r); Thread t2 = new Thread(r); En este caso, ambos hilos comparten los mismos datos y cdigo. Para resumir, un hilo se conoce a travs de una instancia de un objeto Thread. El hilo comienza su ejecucin al inicio del mtodo run de una instancia Runnable cargada. Los datos con los que trabaja el hilo son tomados de la instancia especfica de Runnable, la cual se pasa al constructor de Thread (Figura 15-2).

Figura 15-2 Creacin de un Hilo

Iniciando el Hilo
Un hilo recin creado no inicia corriendo automticamente. Usted debe llamar a su mtodo start. Por ejemplo, usted puede ejecutar el siguiente comando como en la lnea 5 del ejemplo anterior: t.start();

Llamando a start coloca la CPU virtual incorporada en el thread en un estado ejecutable, esto es que se vuelve viable el quela JVM programe su ejecucin. Esto no necesariamente significa que el thread corra inmediatamente.

Programacin de Threads
Por lo general, en la tecnologa Java los hilos son apropiativos, pero no necesariamente de cooperativos (el proceso de brindar a cada hilo una cantidad igual de tiempo de CPU). Es un error comn creer que apropiativos (pre-emptive) es otro trmino para cooperativos (timesliced). El modelo de un programador apropiativo es que varios hilos puedan ser ejecutables, pero slo uno est corriendo. Este hilo contina corriendo hasta que deja de ser ejecutable o hasta que otro hilo de mayor prioridad se vuelva ejecutable. En el segundo caso, el hilo con menor prioridad es adelantado por el hilo de mayor prioridad, el cual tiene otra oportunidad de correr en su lugar. Un hilo puede dejar de ser ejecutable (esto es, volverse bloqueado) por varias razones. El cdigo del hilo puede ejecutar una llamada a Thread.sleep(), pidiendo al hilo que pause deliberadamente por un periodo de tiempo fijo. El hilo podra tener que esperar para accesar a un recurso y no poder continuar hasta que ese recurso est disponible. Todos los hilos que son ejecutables son mantenidos en pools de acuerdo a su prioridad. Cuando un thread bloqueado se vuelve ejecutable, es colocado en la pool ejecutable adecuada. A los threads de las pools no vacas que tengan la ms alta prioridad se les da tiempo de CPU. Un objeto Thread puede existir en varios estados diferentes a travs de su vida como se muestra en la figura 15-3

Figura 15-3 Diagrama de Estado de un Thread Fundamental Aunque el hilo se vuelva ejecutable, no siempre comienza a correr inmediatamente. Slo se realiza una accin a la vez en una mquina con una CPU. Los siguientes prrafos describen cmo son asignados las CPU cuando son ejecutables ms de un hilo. Dado que los hilos de Java no son necesariamente de tiempo compartido, usted debe asegurarse de que el cdigo para su hilo de la oportunidad de vez en cuando a otros hilos de ejecutarse. Esto se puede lograr emitiendo una llamada a sleep en varios intervalos, como se muestra en el Cdigo 15-1. Cdigo 15-1 Ejemplo de programacin de hilos 1 public class Runner implements Runnable { 2 public void run() { 3 while (true) { 4 // do lots of interesting stuff 5 // ... 6 // Give other threads a chance 7 try { 8 Thread.sleep(10); 9 } catch (InterruptedException e) { 10 // This threads sleep was interrupted 11 // by another thread 12 } 13 } 14 } 15 }

El Cdigo 15-1 muestra cmo se usa el bloque try y catch. El mtodo Thread.sleep() y otros mtodos que pueden pausar un hilo por periodos de tiempo se pueden interrumpir. Los hilos pueden llamar al mtodo interrupt de otros hilos, el cual seala al hilo pausado con una InterruptedException. El mtodo sleep es un mtodo static en la clase Thread, porque opera en el hilo actual y es referido como Thread.sleep(x). El argumento del mtodo sleep especifica el nmero mnimo de milisegundos que el hilo debe estar inactivo. La ejecucin del hilo no se reanuda hasta despus de este periodo a menos que sea interrumpido, caso en el cual su ejecucin se reanuda antes.

Terminando un Hilo
Cuando un hijo completa su ejecucin y termina, no puede volver a correr. Usted puede detener un hilo usando una bandera que indica que el mtodo run debe salir. 1 public class Runner implements Runnable { 2 private boolean timeToQuit=false; 3 4 public void run() { 5 while ( ! timeToQuit ) { 6 // do work until we are told to quit 7 } 8 // clean up before run() ends 9 } 10 11 public void stopRunning() { 12 timeToQuit=true; 13 } 14 } 1 public class ThreadController { 2 private Runner r = new Runner();

3 4 5 6 7 8 9 10 11 12 13 }

private Thread t = new Thread(r); public void startThread() { t.start(); } public void stopThread() { // use specific instance of Runner r.stopRunning(); }

En una pieza de cdigo en particular, usted puede referirse al hilo actual usando el mtodo esttico currentThread de la clase Thread. Por ejemplo: 1 public class NameRunner implements Runnable { 2 public void run() { 3 while (true) { 4 // lots of interesting stuff 5 } 6 // Print name of the current thread 7 System.out.println("Thread " 8 + Thread.currentThread().getName() 9 + " completed"); 10 } 11 }

Control Bsico de Threads


Esta seccin describe cmo controlar threads

Probando Hilos
Un hilo puede estar en un estado desconocido. Use el mtodo isAlive para determinar si un thread sigue vivo. El termino vivo no implica que el thread est corriendo; regresa true para un hilo que ha iniciado pero no ha completado su tarea.

Accesando a la Prioridad de los Thread


Use el mtodo getPriority para determinar la prioridad actual del hilo. Use el mtodo setPriority para asignar prioridad a un hilo. La prioridad es un valor entero. La clase Thread incluye las siguientes constantes: Thread.MIN_PRIORITY Thread.NORM_PRIORITY Thread.MAX_PRIORITY

Poniendo Threads en Espera


Existen mecanismos que pueden bloquear temporalmente la ejecucin de un hilo. Usted puede reanudar su ejecucin como si nada hubiera pasado. El thread parece haber ejecutado una instruccin muy lentamente.

El Mtodo Thread.sleep() El mtodo sleep es una manera de detener un hilo por un periodo de tiempo.Recuerde que el thread no reestablece su ejecucin necesariamente al instante en que expira su periodo de dormir. Esto es porque algn otro hilo puede estar ejecutndose en ese instante y no puede ser programado a menos de que algo de lo siguiente ocurra: El hilo que despierta es de una prioridad ms alta. El hilo que corre se bloquee por alguna otra razn.

El Mtodo join El mtodo join hace esperar al thread actual hasta que termine el thread

en el cual se llama el mtodo join. Por ejemplo: 1 public static void main(String[] args) { 2 Thread t = new Thread(new Runner()); 3 t.start(); 4 ... 5 // Do stuff in parallel with the other thread for a while 6 ... 7 // Wait here for the timer thread to finish 8 try { 9 t.join(); 10 } catch (InterruptedException e) { 11 // t came back early 12 } 13 ... 14 // Now continue in this thread 15 ... 16 } Usted tambin puede llamar al mtodo join con un valor de espera (time-out) en milisegundos. Por ejemplo: void join (long timeout); Para este ejemplo, el mtodo join o suspende al hilo actual por tantos milisegundos como indique timeout o hasta que el hilo termine.

El mtodo Thread.yield() Use el mtodo Thread.yield() para dar oportunidad de ejecutarse a otros hilos ejecutables. Si los otros threads son ejecutables, yield coloca al hilo que lo llama en una pool ejecutable y permite a otro hilo ejecutable que corra. Si no hay algn otro hilo ejecutable, yield hace nada. Una llamada a sleep brinda una oportunidad de ejecutarse a hilos de menor prioridad. El mtodo yield brinda una oportunidad de ejecutarse a otros hilos ejecutables.

Otras Formas de Crear Threads


Hasta ahora, usted ha visto cmo crear contextos de hilos con una clase separada que implementa la interfaz Runnable. De hecho, ste no es el nico abordaje. La misma clase Thread implementa la interfaz Runnable, as que usted puede crear un hilo creando una clase que extienda Thread en lugar de que implemente Runnable. 1 public class MyThread extends Thread { 2 public void run() { 3 while ( true ) { 4 // do lots of interesting stuff 5 try { 6 Thread.sleep(100); 7 } catch (InterruptedException e) { 8 // sleep interrupted 9 } 10 } 11 } 12 13 public static void main(String args[]) { 14 Thread t = new MyThread(); 15 t.start(); 16 } 17 }

Seleccionando una Forma de Crear Threads


Dada la eleccin de enfoques para crear un hilo, cmo se puede decidir entre ellos? Cada enfoque tiene sus ventajas, las cuales se describen en esta seccin. Lo siguiente describe las ventajas de implementar Runnable: Desde el punto de vista del diseo orientado a objetos, la clase Thread es estrctamente una encapsulacin de una CPU virtual y,

como tal, debe ser extendida slo cuando usted cambia o extiende el comportamiento de ese modelo de CPU. Por esto y el valor de distinguir entre la CPU, cdigo y las partes de informacin de un hilo en ejecucin, este mdulo del curso usa este enfoque. Usted no puede extender alguna otra clase, como Applet, si usted ya ha extendido de Thread porque la tecnologa Java slo permite herencia nica. En algunos casos, esto le obliga a abordar el problema implementando Runnable. Usted podra preferir ser consistente y siempre implementar Runnable, porque hay ocasiones cuando est obligado a hacerlo de esta manera. La ventaja de extender Thread es que el cdigo tiende a ser ms simple. Nota - Como ambas tcnicas son posibles, usted debe considerar cuidadosamente el por qu extendera Thread. Hgalo slo cuando cambie o extienda el comportamiento de un hilo, no cuando implemente o corra un mtodo.

Usando la Palabra Reservada synchronized


Esta seccin describe el uso de la palabra reservada synchronized. sta provee la lenguaje de programacin Java de un mecanismo que permite a un programador controlar hilos que estn compartiendo informacin.

El Problema
Imagine una clase que representa una pila (stack). Esta clase puede aparecer primero como: 1 public class MyStack { 2 int idx = 0; 3 char [] data = new char[6]; 4 5 public void push(char c) { 6 data[idx] = c; 7 idx++; 8 } 9 10 public char pop() { 11 idx--; 12 return data[idx]; 13 } 14 } La clase no se esfuerza para manejar el desborde (overflow) y subflujo (underflow) de la pila, y la capacidad de la pila es limitada. Sin embargo, estos aspectos no son relevantes para esta discusin. El comportamiento de este modelo requiere que el valor del ndice contenga el subndice de la siguiente celda vaca de la pila. El enfoque de predecremento y postincremento genera esta informacin. Ahora imagine que dos hilos tienen una referencia a una instancia nica de esta clase. Un hilo introduce informacin en la pila y el otro, ms o menos independiente, saca informacin de la pila. En principio, la informacin es aadida y removida exitosamente. Sin embargo, esto es un problema potencial.

Suponga que el hilo a est aadiendo caracteres y que el hilo b est removiendo caracteres. El hilo a ha depositado un caracter, pero an no ha incrementado el ndice del contador. Por alguna razn, este hilo est ahora adelantado. En este punto, el modelo de datos representado en el objeto es inconsistente. buffer idx = 2 Especficamente, la consistencia requiere idx=3 o que el caracter no hubiera sido aadido todava. Si el hilo a contina su ejecucin, podra no haber dao, pero suponga que el hilo b esper para remover un caracter. Mientras el hilo a espera otra oportunidad de correr, el hilo b toma su oportunidad para remover un caracter. Hay una situacin de inconsistencia de informacin en la entrada del mtodo pop, an el mtodo pop procede a decrementar el valor del ndice. buffer idx = 1 Esto sirve efectivamente para ignorar el caracter r. Despus de esto, retorna el caracter q. Hasta ahora el comportamiento ha sido como si el caracter r no hubiera sido introducido, as que es difcil decir que hay algn problema. Pero mire lo que sucede cuando el hilo original, a, contina corriendo. El hilo a prosigue en donde se qued, en el mtodo push, y procede a incrementar el valor del ndice. Ahora tendra lo siguiente: buffer | p | q | r | ^ | | | | p | q | r | ^ | | | | p | q | r | ^ | | |

idx = 2 Esta configuracin implica que q es valido y que la celda que contiene r es la siguiente celda vaca. En otras palabras, q es ledo como si hubiera sido colocado en la pila dos veces, y la letra r nunca aparece. Este es un ejemplo simple de un problema general cuando mltiples hilos accesan informacin compartida. Usted necesita un mecanismo de asegurarse que la informacin compartida se encuentra en un estado consistente antes de que cualquier thread comience a usarla para alguna tarea en particular. Una manera de abordarlo sera evitar que el hilo a sea intercambiado hasta que complete la seccin crtica del cdigo. Este enfoque es comn en programacin de mquinas a bajo nivel pero generalmente es inapropiado para sistemas multi usuarios. Otra manera de abordarlo, y una en la que trabaja la tecnologa Java, es proveer un mecanismo para tratar la informacin de manera delicada. Este enfoque provee un hilo atmico con acceso a informacin sin tener en cuenta si un thread es intercambiado mientras realiza tal acceso.

La Bandera de Bloqueo de Objetos


En la tecnologa Java, cada objeto tiene una bandera asociada con l. Usted puede imaginarse esta bandera como una bandera de bloqueo, La palabra reservada synchronized habilita la interaccin con esta bandera, y provee acceso exclusivo al cdigo que afecta la informacin compartida. Lo siguiente es el fragmento de cdigo modificado: public class MyStack { ... public void push(char c) { synchronized(this) { data[idx] = c; idx++; } } ... }

Cuando el hilo alcance la sentencia synchronized, examina el objeto que es pasado como argumento, e intenta obtener la bandera de bloqueo de ese objeto antes de continuar (vea la figura 15-4).

Figura 15-4 Usando la sentencia synchronized antes de un thread Un ejemplo del uso de la sentencia synchronized despus de un hilo se muestra en la figura 15-5.

Figura 15-5 Usando la sentencia synchronized despus de un hilo Usted debe darse cuenta que esto no protege la informacin. Si el mtodo pop del objeto de informacin compartida no es protegido por synchronized, y es invocado pop por algn otro hilo, an hay riesgo de daar la consistencia de la informacin. Todos los mtodos que accesen informacin compartida deben sincronizarse en el mismo bloqueo para que el bloqueo sea efectivo. La figura 15-6 ilustra lo que pasa cuando el mtodo pop es protegido por la sentencia synchronized y algn otro hilo intenta ejecutar el mtodo

pop de un objeto mientras que el hilo original mantiene la bandera de bloqueo del objeto sincronizado.

Figura 15-6 Hilo Intentando Ejecutar synchronized Cuando el hilo intenta ejecutar la sentencia synchronized (this), ste intenta tomar la bandera de bloqueo del objeto this. El hilo no puede continuar su ejecucin porque la bandera no est presente. El hilo se une entonces a una pool de hilos en espera que estn asociados a la bandera de bloqueo de ese objeto. Cuando la bandera sea devuelta al objeto, se le da al hilo que la estaba esperando, y el hilo contina su ejecucin.

Liberando la Bandera de Bloqueo


Un hilo en espera de la bandera de bloqueo de un objeto no puede continuar su ejecucin hasta que la bandera est disponible. Por lo tanto, es importante que el hilo que retiene la bandera la devuelva cuando ya no la necesita. La bandera de bloqueo es devuelta a su objeto automticamente. Cuando el hilo que retiene el bloqueo pasa el final del bloque de cdigo synchronized por el cual se obtuvo el bloqueo, el bloqueo es liberado, la tecnologa Java se asegura que el bloqueo siempre sea devuelto automticamente, an cuando se haya encontrado una excepcin, una sentencia break, o que una sentencia return transfiera la ejecucin de cdigo fuera de un bloque sincronizado. Tambin, si un hilo ejecuta bloques de cdigo anidados que son sincronizados en el mismo objeto, la bandera del objeto es liberada correctamente en la salida del bloque exterior de cdigo y el bloque interno es ignorado. Estas reglas hacen ms simples de manejar los bloques sincronizados que instalaciones equivalentes de otros sistemas.

Usando synchronized - Todo junto


El mecanismo sincronizado funciona slo si todo acceso a informacin delicada ocurre con bloques sincronizados. Usted debe marcar informacin delicada protegida por bloques sincronizados como private. Si no hace esto la informacin delicada puede ser accesada desde cdigo fuera de la definicin de la clase; tal situacin permitira a otros programadores sobrepasar su proteccin y provocar corrupcin de los datos en tiempo de ejecucin. Un mtodo que consista enteramente de cdigo sincronizado a la instancia this podra poner la palabra reservada synchronized en su cabecera. Los fragmentos de cdigo siguientes son equivalentes: public void push(char c) { synchronized(this) { // The push method code } } public synchronized void push(char c) { // The push method code } Por qu usar una tcnica en lugar de la otra? Si usted usa synchronized como un modificador de mtodo, todo el mtodo se vuelve un bloque sincronizado. sto puede resultar en la retencin de la bandera de bloqueo ms de lo necesario. Sin embargo, marcar el mtodo de esta manera permite a los usuarios del mtodo saber, con la documentacin generada con la utilera javadoc, que se est llevando a cabo sincronizacin. Esto puede ser importante cuando se disea para evitar puntos muertos (deadlocks, que se describirn en la siguiente seccin). El generador de documentacin javadoc propaga el modificador synchronized en sus archivos de documentacin, pero no puede hacer lo mismo con synchronized(this)

, el cual se encuentra dentro del bloque del mtodo.

Estados de los Threads


La sincronizacin es un estado especial de los threads. La figura 15-7 ilustra el nuevo diagrama de transicin de estado para un hilo.

Figura 15-7 Diagrama de Estado de Thread con Sincronizacin

Deadlock
En programas donde varios hilos compiten por accesar a mltiples recursos, puede ocurrir una condicin llamada deadlock (punto muerto). Esto ocurre cuando un hilo est esperando un bloqueo que otro thread est reteniendo, pero ese otro hilo est esperando un bloqueo que ya est retenido por el primer hilo. En esta condicin, ninguno puede proceder hasta que el otro haya pasado el final de su bloque synchronized. Como ninguno puede proceder, ninguno puede pasar al final de su bloque de cdigo. La tecnologa Java ni detecta ni hace el intento por evitar esta condicin. Es responsabilidad del programador asegurarse que no surja ningn punto muerto (deadlock). Como regla general para evitar puntos muertos es: Si usted tiene varios objetos a los que desea que tener acceso sincronizado, tome una decisin global para el orden en el que obtendr los bloqueos, y apguese a ese orden a lo largo del programa. Libere los bloqueos en el orden inverso que los obtuvo.

Interaccin de Threads - wait y notify


Hilos diferentes son creados especficamente para realizar tareas no relacionadas. Sin embargo, hay veces que los trabajos que realizan estn relacionados de alguna manera y puede ser necesario programar cierta interaccin entre ellos.

Escenario
Considere a un chofer de un taxi y a ud. como dos hilos. Usted necesita un taxi para que lo lleve a su destino y el chofer del taxi quiere levantar un pasajero para cobrar la tarifa. As que, cada uno de ustedes tiene una tarea.

El Problema
Usted espera entrar en el taxi y descansar comfortablemente hasta que el chofer le notifique que ha llegado a su destino. Esto puede ser molesto, tanto para usted como para el taxista, preguntar cada 2 segundos, Ya llegamos?. Entre servicio y servicio, el chofer del taxi quiere dormir en el taxi hasta que un pasajero necesite ser llevado a algn lugar. El taxista no quiere despertar de su siesta cada 5 minutos para ver si un pasajero ha llegado al sitio de taxis. As que, ambos threads prefieren realizar sus tareas tan relajado como sea posible.

La Solucin
Usted y el chofer del taxi necesitan una forma de comunicar sus necesidades al otro. Mientras usted est ocupado caminando por la calle hacia el sitio de taxis, el chofer est durmiendo pacficamente en el taxi. Cuando notifica al taxista que desea viajar, el chofer despierta y comienza a conducir, entonces usted se relaja. Despus de que ha llegado a su destino, el chofer le notifica que puede salir del taxi y se va a trabajar. El chofer del taxi espera y duerme de nuevo hasta que el siguiente viaje venga.

Interaccin de Hilos
Esta seccin describe cmo interactan los hilos.

Los mtodos wait y notify.

La clase java.lang.Object provee dos mtodos, wait y notify, para la comunicacin de threads. Si un thread emite una llamada a wait en una reunin con el objeto x, ese hilo pausa su ejecucin hasta que otro hilo emita una llamada a notify en la misma reunin con el objeto x. En el escenario anterior, el chofer del taxi que espera en el taxi se traduce al thread cab driver que ejecuta una llamada a cab.wait(), y su necesidad de usar el taxi se traduce al hilo you ejecutando una llamada a cab.notify(). Para que un hilo llame o a wait o a notify de un objeto, el hilo debe tener bloqueado ese objeto en particular. En otras palabras, wait y notify son llamados slo desde el interior un bloque sincronizado en la instancia en la que estn siendo llamados. Para este ejemplo, usted requiere un bloque de cdigo que comience con synchronized(cab) para permitir la llamada de cab.wait o de cab.notify().

La Historia de Pool Cuando un hilo ejecuta cdigo sincronizado que contenta una llamada a wait en un objeto en particular, ese hilo es colocado en la pool de espera para ese objeto. Adicionalmente, el hilo que llama a wait libera la bandera de bloqueo de ese objeto automticamente. Usted puede invocar mtodos wait diferentes. wait() wait(long timeout) Cuando es ejecutada una llamada a notify en un objeto en particular, un hilo arbitrario se mueve de la pool de espera de ese objeto a una pool de bloqueo, donde permanecen los hilos hasta que la bandera de bloqueo del objeto est disponible de nuevo. El mtodo notifyAll mueve todos los hilos que estn espera de ese objeto fuera de la pool de espera hacia la pool de bloqueo. Slo desde la pool de bloqueo es que un thread puede obtener la bandera de bloqueo de ese objeto, la cual permite al hilo continuar su ejecucin en donde se qued cuando llam a wait.

En muchos sistemas que implementan el mecanismo wait-notify, el thread que despierta es el que ha esperado ms tiempo. Sin embargo, la tecnologa Java no garantiza esto. Usted puede emitir una llamada a notify sin considerar si alguno de los threads estn esperando. Si el mtodo notify es llamado en un objeto cuando no hay threads bloqueados en la pool de espera por la bandera de bloqueo de ese objeto, la llamada no tiene efecto. Las llamadas a notify no se guardan.

Estados de los Hilos


La pool de espera tambin es un estado especial de los hilos. La figura 15-8 ilustra el que finalmente es el diagrama de transicin de estado para un hilo.

Figura 15-8 Diagrama de Estados de Thread con wait y notify

Modelo de Monitor para Sincronizacin


La coordinacin entre dos hilos que necesitan acceso a informacin en comn puede volverse complicada. Usted debe asegurarse que ningn thread deje informacin compartida en un estado inconsistente cuando exista la posibilidad que otro thread pueda accesar esa informacin. Usted debe asegurarse tambin que su programa no llegue a un punto muerto, porque los threads no pueden liberar el bloqueo apropiado cuando otros threads estn esperando ese bloqueo. En el ejemplo del taxi, el cdigo basado en un objeto de convergencia, el taxi, en el que wait y notify fueron ejecutados. Si alguien espera un autobs, usted necesitara un objeto separado bus al cul aplique notify. Recuerde que todos los hilos en la misma pool de espera deben satisfacerse mediante una notificacin del objeto que controla esa pool de espera. Nunca disee cdigo que ponga threads en espera de notificaciones de condiciones diferentes en la misma pool de espera.

Poniendo todo junto


El cdigo en esta seccin es un ejemplo de la interaccin de threads que demuestra el uso de los mtodos wait y notify para resolver un problma clsico de productor-consumidor. Comience viendo el esquema del objeto pila y los detalles de los threads que lo accesan. Luego mire los detalles de la pila y el mecanismo usado para proteger la informacin de la pila y para implementar la comunicacin de threads basada en el estado de la pila. La clase ejemplo de pila, llamada SyncStack para distinguirla de la clase core java.util.Stack, ofrece la siguiente API pblica: public synchronized void push(char c); public synchronized char pop();

El Hilo Productor
El hilo productor genera caracteres nuevos que sern colocados en la pila. El cdigo 15-2 muestra la clase Producer Cdigo 15-2 La clase Producer 1 package mod13; 2 3 public class Producer implements Runnable { 4 private SyncStack theStack; 5 private int num; 6 private static int counter = 1; 7 8 public Producer (SyncStack s) { 9 theStack = s; 10 num = counter++; 11 } 12 13 public void run() { 14 char c; 15 16 for (int i = 0; i < 200; i++) { 17 c = (char)(Math.random() * 26 +A); 18 theStack.push(c); 19 System.out.println(Producer + num + : + c); 20 try { 21 Thread.sleep((int)(Math.random() * 300)); 22 } catch (InterruptedException e) { 23 // ignore it 24 } 25 } 26 } // END run method 27 28 } // END Producer class Este ejemplo genera 200 caracteres aleatorios y los introduce a la pila con un retraso aleatorio de 0-300 milisegundos entre cada insercin. Cada

caracter introducido es reportado a la consola, junto con un identificador del hilo productor que est en ejecucin.

El thread Consumidor
El hilo consumidor retira los caracteres de la pila. El cdigo 15-3 muestra la clase Consumer. Cdigo 15-3 La clase Consumer 1 package mod13; 2 3 public class Consumer implements Runnable { 4 private SyncStack theStack; 5 private int num; 6 private static int counter = 1; 7 8 public Consumer (SyncStack s) { 9 theStack = s; 10 num = counter++; 11 } 12 13 public void run() { 14 char c; 15 for (int i = 0; i < 200; i++) { 16 c = theStack.pop(); 17 System.out.println(Consumer + num + : + c); 18 19 try { 20 Thread.sleep((int)(Math.random() * 300)); 21 } catch (InterruptedException e) { 22 // ignore it 23 } 24 } 25 } // END run method 26 27 } // END Consumer class Este ejemplo recolecta 200 caracteres de la pila, con un retraso aleatorio

de 0-300 milisegundos entre cada intento. Cada caracter maysculo es reportado en la consola, junto con un identificador para identificar el hilo consumidor que se est ejecutando. Ahora considere la construccin de la clase pila. Usted crear una pila que tenga un tamao infinito aparente, usando la clase ArrayList. Con este diseo, sus hilos se comunicarn slo basndose en si la pila est vaca.

La clase SyncStack
El buffer de un objeto SyncStack recin construido debe estar vaco. Usted puede usar el siguiente cdigo para construir su clase: public class SyncStack { private List<Character> buffer = new ArrayList<Character>(400); public synchronized char pop() { // pop code here } public synchronized void push(char c) { // push code here } } No hay constructores. Es considerado buena prctica el incluir un constructor, pero ha sido omitido para abreviar.

El mtodo pop Ahora considere los mtodos push y pop. stos deben estar sincronizados para proteger el buffer compartido. Adems, si la pila est vaca en el mtodo pop, el hilo que lo ejecuta debe esperar. Cuando la pila en el mtodo push ya no est vaca, se notifica a los hilos que esperan. El cdigo 15-4 muestra el mtodo pop Cdigo 15-4 El Mtodo pop 1 public synchronized char pop() { 2 char c; 3 while (buffer.size() == 0) { 4 try { 5 this.wait(); 6 } catch (InterruptedException e) { 7 // ignore it... 8 } 9 } 10 c = buffer.remove(buffer.size()-1); 11 return c; 12 } La llamada al mtodo wait se realiza con respecto al objeto pila que muestra cmo la reunin se realiza con un objeto en particular. Nada puede ser retirado de la pila cuando est vaca, as un hilo que intenta sacar informacin de la pila debe esperar hasta que la pila ya no est vaca. La llamada a wait tiene lugar dentro de un bloque try-catch porque una llamada a interrupt puede terminar con el periodo de espera del hilo. El mtodo wait tambin debe estar dentro de un ciclo para este ejemplo. Si por alguna razn (como una interrupcin) el hilo despierta y descubre que la pila sigue vaca, entonces el hilo debe volver al estado de espera. Para la pila el mtodo pop es sincronizado por dos razones. Primera, retirando un caracter de la pila afecta el buffer de informacin

compartida. Segunda, la llamada a this.wait() debe estar en un bloque sincronizado en el objeto pila, el cul se representa con this. El mtodo push usa this.notify() para liberar a un hilo de la pool de espera del objeto pila. Despus de que es liberado un hilo, puede ser obtenido el bloqueo en la pila y continuar ejecutando el mtodo pop, el cual retira un caracter del buffer de la pila.

Nota - En pop, el mtodo wait es llamado antes de que cualquier caracter sea removido de la pila. Esto es porque la remocin no puede proceder hasta que algn caracter est disponible.

Usted tambin debe considerar la verificacin de errores. Usted debe haber notado que no hay cdigo explcito que prevenga un desborde de pila. Esto no es necesario porque la nica forma de retirar caracteres de la pila es a travs del mtodo pop, y este mtodo hace que el hilo que se ejecuta entre a un estado de espera si no hay algn caracter disponible. Por lo tanto, la verificacin de errores no es necesaria.

El mtodo push El mtodo push es similar al mtodo pop. Afecta el buffer compartido y tambin debe estar sincronizado. Adems es responsable de notificar a los hilos que esperan por una pila que no est vaca ya que el mtodo push inserta caracteres al buffer. Esta notificacin se realiza con respecto al objeto pila. El cdigo 15-5 muestra el mtodo push Cdigo 15-5 El Mtodo push 1 public synchronized void push(char c) { 2 this.notify(); 3 buffer.add(c); 4 } 5

La llamada a this.notify() sirve para liberar un nico hilo que llam a wait porque la pila est vaca. Llamando a notify antes de que la informacin compartida cambie no tiene consecuencia. El bloqueo del objeto pila es liberado slo hasta la salida del bloque sincronizado, as que los hilos en espera de ese bloqueo lo pueden obtener mientras la informacin en la pila est siendo cambiada por el mtodo pop. Poniendo todas las piezas juntas, el cdigo 15-6 muestra la clase SyncStack completa. Cdigo 15-6 La Clase SyncStack 1 package mod13; 2 3 import java.util.*; 4 5 public class SyncStack { 6 private List<Character> buffer 7 = new ArrayList<Character>(400); 8 9 public synchronized char pop() { 10 char c; 11 while (buffer.size() == 0) { 12 try { 13 this.wait(); 14 } catch (InterruptedException e) { 15 // ignore it... 16 } 17 } 18 c = buffer.remove(buffer.size()-1); 19 return c; 20 } 21 22 public synchronized void push(char c) { 23 this.notify(); 24 buffer.add(c); 25 } 26 }

El Ejemplo SyncTest
Usted debe ensamblar el cdigo del productor, consumidor y la pila en clases completas. Es requerida una prueba que una todas estas piezas. Preste especial atencin en cmo SyncTest crea slo un objeto pila que es compartido por todos los hilos. El cdigo 15-7 muestra la clase SyncTest. Cdigo 15-7 La clase SyncTest 1 package mod13; 2 3 public class SyncTest { 4 5 public static void main(String[] args) { 6 7 SyncStack stack = new SyncStack(); 8 9 Producer p1 = new Producer(stack); 10 Thread prodT1 = new Thread (p1); 11 prodT1.start(); 12 13 Producer p2 = new Producer(stack); 14 Thread prodT2 = new Thread (p2); 15 prodT2.start(); 16 17 Consumer c1 = new Consumer(stack); 18 Thread consT1 = new Thread (c1); 19 consT1.start(); 20 21 Consumer c2 = new Consumer(stack); 22 Thread consT2 = new Thread (c2); 23 consT2.start(); 24 } 25 } Lo siguiente es un ejemplo de la salida de java mod13.SyncTest. Cada vez que el cdigo de este hilo corre, los resultados varan

Producer2: Consumer1: Producer2: Consumer2: Producer2: Producer1: Producer1: Consumer2: Consumer1: Producer2: Producer2: Consumer2: Consumer2: Producer1: Consumer1: Producer2: Consumer2: Consumer2:

F F K K T N V V N V U U V F F M M T

You might also like