Threads en Java
Threads en Java
Threads en Java
1) Qu es un Thread?
2) Un Ejemplo Sencillo de Thread.
3) Atributos de un Thread
4) El Cuerpo de un Thread
5) Un Applet de un Reloj Digital
Decidir Utilizar la Interfaz Runnable
La Interfaz Runnable
Crear el Thread
Parar el Thread
El Mtodo Run
6) El Estado de un Thread
Un "Nuevo Thread"
Ejecutable
No Ejecutable
Muerto
La Excepcin IllegalThreadStateException
El Mtodo isAlive()
7) Prioridad de un Thread
La carrera de Threads
Threads Egostas
Tiempo-Compartido
Sumario
8) Threads Servidores
9) Grupos de Threads
El Grupo de Threads por Defecto
Crear un Thread en un Grupo Especfico
Obtener el Grupo de un Thread
La Clase ThreadGroup
10) La Clase ThreadGroup
Mtodos de Manejo de la Coleccin
Mtodos que Operan sobre el Grupo
Mtodos que Operan con Todos los Threads de un Grupo.
Mtodos de Restriccin de Acceso
11) Programas con Varios Threads
Sincronizacin de Threads
Imparcialidad, Hambre y Punto Muerto
Volatile
12) Sincronizacin de Threads
El Ejemplo Productor/Consumidor
Monitores
Los mtodos notify() y wait()
El programa Principal
La Salida
13) Monitores Java
14) Los Monitores Java son Re-Entrantes
15) Los Mtodos Wait() y Notify()
El mtodo notify()
El mtodo wait()
Los Monitores y los Mtodos notify() y wait()
16) Desafo
1) Qu es un Thread?
Definicin:
No hay nada nuevo en el concepto de un slo thread. Pero el juego real alrededor de los threads no est sobre los threads
secuenciales solitarios, sino sobre la posibilidad de que un solo programa ejecute varios threads a la vez y que realicen diferentes
tareas.
El navegador HotJava es un ejemplo de una aplicacin multi-thread. Dentro del navegador HotJava puedes moverte por la pgina
mientras bajas un applet o una imagen, se ejecuta una animacin o escuchas un sonido, imprimes la pgina en segundo plano
mientras descargas una nueva pgina, o ves cmo los tres algoritmos de ordenacin alcanzan la meta.
Algunos textos utilizan el nombre proceso de poco peso en lugar de thread.
Un thread es similar a un proceso real en el que un thread y un programa en ejecucin son un slo flujo secuencial de control. Sin
embargo, un thread se considera un proceso de poco peso porque se ejecuta dentro del contexto de un programa completo y se
aprovecha de los recursos asignados por ese programa y del entorno de ste.
Como un flujo secuencial de control, un thread debe conseguir algunos de sus propios recursos dentro de un programa en
ejecucin. (Debe tener su propia pila de ejecucin y contador de programa, por ejemplo). El cdigo que se ejecuta dentro de un
Thread trabaja slo en ste contexto. As, algunos texto utilizan el trmino contexto de ejecucin como un sinnimo para los
threads.
Este ejemplo define dos clases: SimpleThread y TwoThreadsTest. Empecemos nuestra exploracin de la aplicacin con la clase
SimpleThread -- una subclase de la clase Thread, que es proporcionada por el paquete java.lang.
class TwoThreadsTest {
public static void main (String[] args) {
new SimpleThread("Jamaica").start();
new SimpleThread("Fiji").start();
}
}
El mtodo main() tambin arranca cada uno de los threads inmediatamente despus siguiendo su construccin con una llamada al
mtodo start(). El programa dara una salida parecida a esta.
0 Jamaica
0 Fiji
1 Fiji
1 Jamaica
2 Jamaica
2 Fiji
3 Fiji
3 Jamaica
4 Jamaica
4 Fiji
5 Jamaica
5 Fiji
6 Fiji
6 Jamaica
7 Jamaica
7 Fiji
8 Fiji
9 Fiji
8 Jamaica
HECHO! Fiji
9 Jamaica
HECHO! Jamaica
Observa cmo la salida de cada uno de los threads se mezcla con la salida del otro. Esto es porque los dos threads SimpleThread
se estn ejecutando de forma concurrente. As, los dos mtodos run() se stn ejecutando al mismo tiempo y cada thread est
mostrndo su salida al mismo tiempo que el otro.
Prueba esto: Modifica el programa principal y crea un tercer Thread llamado "Argentina".
Compila el programa y ejectalo de nuevo. Ha cambiado el destino de tus vacaciones?
Si modificamos la clase TwoThreadsTest de la siguiente forma y volvemos a compilar y ejecutar el programa.
class TwoThreadsTest {
public static void main (String[] args) {
new SimpleThread("Jamaica").start();
new SimpleThread("Fiji").start();
System.out.println("Listo!, ya me decidi.");
}
}
Es posible que el programa principal, finalice su ejecucin antes que los hilos lanzados dentro del metodo main?
Modifique el programa apropiadamente para que la decisin muestre cual fue el destino elegido y luego finalice el
programa.
3) Atrributos de un Thread
Por ahora, te has familiarizado con los threads y has visto un sencillo programa Java que ejecuta dos thread concurrentemente.
Esta pgina presenta varias caractersticas especficas de los threads Java y proporciona enlaces a las pginas que explican cada
caracterstica con ms detalle.
Los threads en java estn implementados por la clase Thread, que es una clase del paquete java.lang.
Esta clase implementa una definicin de threads independiente del sistema. Pero bajo la campana, la implementacin real de la
operacin concurrente la proporciona una implementacin especfica del sistema. Para la mayora de las aplicaciones, la
implementacin bsica no importa. Se puede ignorar la implementacin bsica y programar el API de los thread descrito en estas
lecciones y en otra documentacin proporcionada con el sistema Java.
Cuerpo del Thread
Toda la accin tiene lugar en el cuerpo del thread -- el mtodo run().
Se puede proporcionar el cuerpo de un Thread de una de estas dos formas: especializando la clase Thread y
sobreescribiendo su mtodo run(), o creando un thread utilizando la clase Runnable y su target.
Estado de un Thread
A lo largo de su vida, un thread tiene uno o varios estados. El estado de un thread indica qu est haciendo el Thread y lo
que es capaz de hacer durante su tiempo de vida.
se est ejecutando?, est esperando? o est muerto?
La prioridad de un Thread
Una prioridad del Thread le dice al temporizador de threads de Java cuando se debe ejecutar este thread en relacin con
los otros.
Threads Daemon
Estos threads son aquellos que porporcionan un servicio para otros threads del sistema.
Cualquier thread Java puede ser un thread daemon.
Grupos de Threads
Todos los threads pertenecen a un grupo. La clase ThreadGrpup, perteneciente al paquete java.lang define e implementa
las capacidades de un grupo de thread relacionados.
4) El Cuerpo de un Thread
Toda la accin tiene lugar en el cuerpo del thread, que es el mtodo run() del thread. Despus de crear e inicializar un thread, el
sistema de ejecucin llama a su mtodo run(). El cdigo de este mtodo implementa el comportamiento para el que fue creado el
thread. Es la razn de existir del thread.
Frecuentemente, el mtodo run() de un thread es un bucle. Por ejemplo, un thread de animacin podra iterar a travs de un bucle y
mostrar una serie de imgenes. Algunas veces un mtodo run() realiza una operacin que tarda mucho tiempo, como descargar y
ejecutar un sonido o una pelcula JPEG.
Puedes elegir una de estas dos formas para proporcionar un mtodo run() a un thread Java.
1. Crear una subclase de la clase Thread definida en el paquete java.lang y sobreescribir el mtodo run().
Si tu clase debe ser una subclase de otra clase (el ejemplo ms comn son lo applets), debers utilizar Runnable.
6) El Estado de un Thread
El siguiente diagrama ilustra los distintos estados que puede tener un Thread Java en cualquier momento de su vida. Tambin
ilustra las llamadas a mtodos que provocan las transiciones de un estado a otro. Este no es un diagrama de estado finito pero da
un idea general del las facetas ms interesantes y comunes en la vida de un thread. El resto de est pgina explica el ciclo de vida
de un thread, basndose en sus estados.
Un "Nuevo Thread"
La siguiente sentencia crea un nuevo thread pero no lo arranca, por lo tanto deja el thread en el estado : "New Thread" = "Nuevo
Thread".
Thread miThread = new MiClaseThread();
Cuando un thread est en este estado, es slo un objeto Thread vaco. No se han asignado recursos del sistema todava para el
thread. As, cuando un thread est en este estado, lo nico que se puede hacer es arrancarlo o pararlo. Llamar a otros mtodos
distintos de start() o stop() no tiene sentido y causa una excepcin del tipo IllegalThreadStateException.
Ejecutable
Ahora consideremos estas dos lneas de cdigo.
Thread miThread = new MiClaseThread();
miThread.start();
Cuando el mtodo start() crea los recursos del sistema necesarios para ejecutar el thread, programa el thread para ejecutarse, y
llama al mtodo run() del thread. En este punto el thread est en el estado "Ejecutable". Este estado se llama "Ejecutable" mejor
que "Ejecutando" ya que el thread todava no ha empezado a ejecutarse cuando est en este estado. Muchos procesadores tienen
un slo procesador, haciendo posible que todos los threads sean "Ejecutables" al mismo tiempo. Por eso, el sistema de ejecucin
de Java debe implementar un esquema de programacin para compartir el procesador entre todos los threads "Ejecutables".
Sin embargo, para la mayora de los propositos puedes pensar en "Ejecutable" como un sencillo "Ejecutando". Cuando un thread se
est ejecutanto -- est "Ejecutable" y es el thread actual -- las instrucciones de su mtodo run()se ejecutan de forma secuencial.
No Ejecutable
Un thread entra en el estado "No Ejecutable" cuando ocurre uno de estos cuatro eventos.
Alguien llama a su mtodo sleep().
Alguien llama a su mtodo suspend().
El thread utiliza su mtodo wait() para esperar una condicin variable.
El thread est bloqueado durante la I/O.
Por ejemplo, la lnea en negrita del siguiente fragmento de codigo pone a dormir miThread durante 10 segundos (10.000
milisegundos).
Thread miThread = new MiClaseThread();
miThread.start();
try {
miThread.sleep(10000);
} catch (InterruptedException e){
}
Durante los 10 segundos que miThread est dormido, incluso si el proceso se vuelve disponible miThread no se ejecuta. Despus
de 10 segundos, miThread se convierte en "Ejecutable" de nuevo y, si el procesar est disponible se ejecuta.
Para cada entrada en el estado "No Ejecutable" mostrado en figura, existe una ruta de escape distinta y especfica que devuelve el
thread al estado "Ejecutable". Una ruta de escape slo trabaja para su entrada correspondiente. Por ejemplo, si un thread ha sido
puesto a dormir dutante un cierto nmero de milisegundos deben pasar esos milisegundos antes de volverse "Ejecutable" de nuevo.
Llamar al mtodo resume() en un thread dormido no tiene efecto.
Esta lista indica la ruta de escape para cada entrada en el estado "No Ejecutable".
Si se ha puesto a dormir un thread, deben pasar el nmero de milisegundos especificados.
Si se ha suspendido un thread, alguien debe llamar a su mtodo resume().
Si un thread est esperando una condicin variable, siempre que el objeto propietario de la variable renuncie mediante
notify() o notifyAll().
Si un thread est bloqueado durante la I/O, cuando se complete la I/O.
Muerto
Un thread puede morir de dos formas: por causas naturares o siendo asesinado (parado). Una muerte natural se produce cuando
su mtodo run() sale normalmente. Por ejemplo, el bucle while en este mtodo es un bucle finito -- itera 100 veces y luego sale.
public void run() {
int i = 0;
while (i < 100) {
i++;
System.out.println("i = " + i);
}
}
Un thread con este mtodo run() morira natualmente despus de que el bucle y el mtodo run() se hubieran completado.
Tambin puede matar un thread en cualquier momento llamando a su mtodo stop(). El siguiente cdigo crea y arranca miThread
luego lo pone a dormir durante 10 segundos. Cuando el thread actual se despierta, la lnea en negrita mata miThread.
Thread miThread = new MiClaseThread();
miThread.start();
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e){
}
miThread.stop();
El mtodo stop() lanza un objeto ThreadDeath hacia al thread a eliminar. As, cuando se mata al thread de esta forma, muere de
forma asncrona. El thread moriri cuando reciba realmente la excepcin ThreadDeath.
El mtodo stop() provoca una terminacin sbita del mtodo run() del thread. Si el mtodo run() estuviera realizando clculos
sensibles, stop() podra dejar el programa en un estado inconsistente. Normalmente, no se debera llamar al mtodo stop() pero si
se debera proporcionar una terminacin educada como la seleccin de una bandera que indique que el mtodo run() debera salir.
La Excepcin IllegalThreadStateException
El sistema de ejecucin lanza una excepcin IllegalThreadStateException cuando llama a un mtodo en un thread y el estado del
thread no pemmite esa llamada a mtodo.
Por ejemplo, esta excepcin se lanza cuando se llama a suspend() en un thread que no est "Ejecutable".
Como se ha mostrado en varios ejemplos de threads en est leccin, cuando se llame a un mtodo de un thread que pueda lanzar
una excepcin, se debe capturar y manejar la excepcin, o especificar al mtodo llamador que se lanza la excepcin no capturada.
El Mtodo isAlive()
Una ltima palabra sobre el estrado del thread: el interface de programacin de la clase Thread incluye un mtodo llamado
isAlive(). Este mtodo devuelve true si el thread ha sido arrancado y no ha parado. As, si el mtodo isAlive() devuelve false
sabrs que se trata de un "Nuevo thread" o de un thread "Muerto". Por el contrario si devuelve true sabrs que el thread es
"Ejecutable" o "No Ejecutable". No se puede diferenciar entre un "Nuevo thread" y un thread "Muerto", como tampoco se puede
hacer entre un thread "Ejecutable" y otro "No Ejecutable"
7) Prioridad de un Thread
Anteriormente, hemos reclamado que los applets se ejecuten de forma concurrente.
Mientras conceptualmente esto es cierto, en la prctica no lo es. La mayora de las configuraciones de ordenadores slo tienen una
CPU, por eso los threads realmente se ejecutan de uno en uno de forma que proporcionan una ilusin de concurrencia. La
ejecucin de varios threads en una sola CPU, en algunos rdenes, es llamada programacin.
El sistema de ejecucin de Java soporta un algoritmo de programacin deterministico muy sencillo conocido como programacin de
prioridad fija. Este algoritmo programa los threads basndose en su prioridad relativa a otros threads "Ejecutables".
Cuando se crea un thread Java, hereda su prioridad desde el thread que lo ha creado. Tambin se puede modificar la prioridad de
un thread en cualquier momento despus de su creaccin utilizando el mtodo setPriority(). Las prioridades de un thread son un
rango de enteros entre MIN_PRIORITY y MAX_PRIORITY (constantes definidas en la clase Thread). El entero ms alto, es la
prioridad ms alta. En un momento dado, cuando varios threads est listos para ser ejecutados, el sistema de ejecucin elige
aquellos thread "Ejecutables" con la prioridad ms alta para su ejecucin. Slo cuando el thread se para, abandona o se convierte
en "No Ejecutable" por alguna razn empezar su ejecucin un thread con prioridad inferior. Si dos threads con la misma prioridad
estn esperando por la CPU, el programador elige uno de ellos en una forma de competicin. El thread elegido se ejecutar hasta
que ocurra alguna de las siguientes condiciones.
Un thread con prioridad superior se vuelve "Ejecutable".
Abandona, o su mtodo run() sale.
En sistemas que soportan tiempo-compartido, su tiempo ha expirado.
Luego el segundo thread tiene una oprtunidad para ejecutarse, y as continuamente hasta que el interprete abandone.
El algoritmo de programacin de threads del sistema de ejecucin de Java tambin es preemptivo. Si en cualquier momento un
thread con prioridad superior que todos los dems se vuelve "Ejecutable", el sistema elige el nuevo thread con prioridad ms alta.
Se dice que el thread con prioridad superior prevalece sobre los otros threads.
La carrera de Threads
Este cdigo fuente implementa un applet que anima una carrera entre dos threads "corredores" con diferentes prioridades. Cuando
pulses con el ratn sobre el applet, arrancan los dos corredores. El corredor superior , llamado "2", tiene una prioridad 2.
El segundo corredor, llamado "3", tiene una prioridad 3.
Prueba esto: Pulsa sobre el applet inferior para iniciar la carrera.
Este es el mtodo run() para los dos corredores.
public int tick = 1;
public void run() {
while (tick < 400000) {
tick++;
}
}
Este mtodo slo cuenta desde 1 hasta 400.000. La variable tick es pblica porque la utiliza el applet para determinar cuanto ha
progresado el corredor (cmo de larga es su lnea).
Adems de los dos threads corredores, el applet tiene un tercer thread que controla el dibujo.
El mtodo run() de este thread contiene un bucle infinito; durante cada iteracin del bucle dibuja una lnea para cada corredor (cuya
longitud se calcula mediante la variable tick), y luego duerme durante 10 milisegundos. Este thread tiene una prioridad de 4 --
superior que la de los corredores. Por eso, siempre que se despierte cada 10 milisegundos, se convierte en el thread de mayor
prioridad, prevalece sobre el thread que se est ejecutando, y dibuja las lneas.
Se puede ver cmo las lneas van atravesando la pgina.
Como puedes ver, esto no es una carrera justa porque un corredor tiene ms prioridad que el otro.
Cada vez que el thread que dibuja abandona la CPU para irse a dormir durante 10 milisegundos, el programador elige el thread
ejecutable con una prioridad superior; en este caso, siempre ser el corredor llamado "3". Aqu tienes otra versin del applet que
implementa una carrera justa, esto es, los dos corredores tienen la misma prioridad y tienen las mismas posibilidades para ser
elegidos.
Prueba esto: Pulsa sobre el Applet para iniciar la carrera.
En esta carrera, cada vez que el thread de dibujo abandona la CPU, hay dos threads ejecutables con igual prioridad -- los
corredores -- esperando por la CPU; el programador debe elegir uno de los threads. En esta situacin, el programador elige el
siguiente thread en una especie de competicin deportiva.
Threads Egoistas
La clase Runner utilizada en las carreras anteriores realmente implementea un comportamiendo "socialmente-perjudicioso".
Recuerda el mtodo run() de la clase Runner utilizado en las carreras.
public int tick = 1;
public void run() {
while (tick < 400000) {
tick++;
}
}
El bucle while del mtodo run() est en un mtodo ajustado.
Esto es, una vez que el programador elige un thread con este cuerpo de thread para su ejecucin, el thread nunca abandona
voluntariamente el control de la CPU -- el thread se contina ejecutando hasta que el bucle while termina naturalmente o hasta que
el thread es superado por un thread con prioridad superior.
En algunas situaciones, tener threads "egoistas" no causa ningn problema porque prevalencen los threads con prioridad superior
(como el thread del dibujo prevalece sobres los threads egoistas de los corredores. Sin embargo, en otras situaciones, los threads
con mtodos run() avariciosos de CPU, como los de la clase Runner, pueden tomar posesin de la CPU haciendo que otros
threads esperen por mucho tiempo antes de obtener una oportunidad para ejecutarse.
Tiempo-Compartido
En sistemas, como Windows 95, la lucha contra el comportamiento egoista de los threads tiene una estrategia conocida como
tiempo-compartido. Esta estrategia entra en juego cuando existen varios threads "Ejecutables" con igual prioridad y estos threads
son los que tienen una prioridad mayor de los que estn compitiendo por la CPU. Por ejemplo, este programa java (que est
basado en la carrera de Applets anterior) crea dos threads egoistas con la misma prioridad que tienen el siguiente todo run().
public void run() {
while (tick < 400000) {
tick++;
if ((tick % 50000) == 0) {
System.out.println("Thread #" + num + ", tick = " + tick);
}
}
}
Este mtodo contiene un bucle ajustado que incrementa el entero tick y cada 50.000 ticks imprime el indentificador del thread y su
contador tick.
Cuando se ejecuta el programa en un sistema con tiempo-compartido, vers los mensajes de los dos threads, intermitentemente
uno y otro. Como esto.
Thread #1, tick = 50000
Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #1, tick = 250000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 400000
Esto es porque un sistema de tiempo compartido divide la CPU en espacios de tiempo e iterativamente le da a cada thread con
prioridad superior un espacio de tiempo para ejecutarse. El sistema de tiempo compartido itera a travs de los threads con la misma
prioridad superior otorgndoles un pequeo espacio de tiempo para que se ejecuten, hasta que uno o ms de estos threads
finalizan, o hasta que aparezca un thread con prioridad superior. Observa que el tiempo compartido no ofrece garantias sobre la
frecuencia y el orden en que se van a ejecutar los threads.
Cuando ejecutes este programa en un sistema sin tiempo compartido, sin embargo, veras que los mensajes de un thread terminan
de imprimierse antes de que el otro tenga una oportunidad de mostrar un slo mensaje. Como esto.
Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #1, tick = 350000
8) Threads Servidores
Cualquier thread Java puede ser un thread daemon "Servidor". Los threads daemon proporcionan servicios para otros threads que
se estn ejecutando en el mismo proceso que l. Por ejemplo, el navegador HotJava utiliza cuatro threads daemon llamados "Image
Fetcher" para buscar imgenes en el sistema de archivos en la red para los threads que las necesiten. El mtodo run() de un thread
daemon normalmente es un bucle infinito que espera una peticin de servicio.
Cuando el nico thread en un proceso es un thread daemon, el interprete sale. Esto tiene sentido porque al permanecer slo el
thread daemon, no existe ningn otro thread al que poder proporcinale un servicio.
Para especificar que un thread es un thread daemon, se llama al mtodo setDaemon() con el argumento true. Para determinar si
un thread es un thread daemon se utiliza el mtodo accesor isDaemon().
9) Grupos de Threads
Cada thread de Java es miembro de un grupo de threads. Los grupos proporcionan un mecanismo para la coleccin de varios
threads dentro de un slo objeto y la manipulacin de esos threads de una vez, mejor que de forma individual. Por ejemplo, se
puede arrancar o suspender todos los threads de un grupo con una sla llamada a un mtodo. Los grupos de threads de Java estn
implementados por la clase ThreadGroup del paquete java.lang.
El sistema de ejecucin pone un thread dentro de un grupo durante su construccin. Cuando se crea un thread, tambin se puede
permitir que el sistema de ejecucin ponga el nuevo thread en algn grupo por defecto razonable o se puede especificar
explicitamente el grupo del nuevo thread. El thread es un miembro permanente del grupo al que se uni durante su creaccin -- no
se puede mover un thread a otro grupo despus de haber sido creado.
Si se crea un nuevo Thread sin especificar su grupo en el constructor, el sistema de ejecucin automticamente sita el nuevo
thread en el mismo grupo que del thread que lo cre (conocido como el grupo de threads actual y el thread actual,
respectivamente).
Entonces, si se deja sin especificar el grupo de threads cuando se crea un thread, qu grupo contiene el thread?
Cuando se arranca por primera vez una aplicacin Java, el sistema de ejecucin crea un ThreadGroup llamado "main". Entonces, a
menos que se especifique otra cosa, todos los nuevos threads que se creen se convierten en miembros del grupo de threads
"main".
Nota:
Si se crea un thread dentro de un applet, el grupo del nuevo thread podra ser distinto de "main" -- depende del navegador o del
visualizador donde se est ejecutando al applet. Puedes referirse a Threads en Applets para obtener ms informacin sobre los
grupos de threads en los applets.
Muchos programadores java ignoran por completo los grupos de threads y permiten al sistema de ejecucin que maneje todos lo
detalles con respecto a los grupos de threads. Sin embargo, si tu programa crea muchos threads que deben ser manipulados como
un grupo, o si ests implementando un Controlador de Seguridad de cliente, probablemente querrs ms control sobre los grupos
de threads.
Como se mencion anteriormente, un thread es un miembro permanente del grupo al que se uni cuando fue creado -- no se puede
mover un thread a otro grupo despues de haber sido creado.
As, si se desea poner un nuevo thread en un grupo distinto al de defecto, se debe especificar explcitamente el grupo en cual crear
el thread. La clase Thread tiene tres constructores que permiten seleccionar un nuevo grupo de threads.
public Thread(ThreadGroup grupo, Runnable fuente)
public Thread(ThreadGroup grupo, String nombre)
public Thread(ThreadGroup grupo, Runnable fuente, String nombre)
Cada uno de estos constructores crea un nuevo thread, lo inicializa basandose en los parmetros Runnable y String, y lo hace
miembro del grupo especificado. Por ejemplo, el siguiente ejemplo crea un grupo de threads (miGrupoDeThread) y luego crea un
thread (miThread) en ese grupo.
ThreadGroup miGrupoDeThread = new ThreadGroup("Mi Grupo de Threads");
Thread miThread = new Thread(miGrupoDeThread, "un thread de mi grupo");
El ThreadGroup pasado al constructor del Thread no tiene que ser necesariamente un grupo que hayas creado -- puede ser un
grupo creado por el sistema de ejecucin Java, o un grupo creado por la aplicacin donde se est ejecutando el applet.
Para encontrar el grupo en el que est un thread, puede llamar a su mtodo getThreadGroup().
theGroup = miThread.getThreadGroup();
numThreads = currentGroup.activeCount();
listOfThreads = new Thread[numThreads];
currentGroup.enumerate(listOfThreads);
for (int i = 0; i < numThreads; i++) {
System.out.println("Thread #" + i + " = " + listOfThreads[i].getName());
}
}
}
Otros mtodos de manejo de la coleccin proporcionados por la clase ThreadGroup incluyen activeGroupCount() y list().
Mtodos que Operan sobre el Grupo
La clase ThreadGroup soporta varios atributos que son seleccionados y recuperados para el grupo en su totalidad. Estos atributos
incluyen la prioridad mxima que un thread puede tener dentro del grupo, si el grupo es un grupo "daemon", el nombre del grupo, y
el nombre del padre del grupo.
Los mtodos que seleccionan y obtienen estos atributos operan a nivel de grupo. Esto es, pueden inspeccionar el atributo del objeto
ThreadGroup, pero no afectan a los threads que hay dentro del grupo.
El siguiente listado muestra los mtodos de ThreadGropup que operan a nivel de grupo.
getMaxPriority(), y setMaxPriority()
getDaemon(), y setDaemon()
getName()
getParent(), y parentOf()
toString()
As, por ejemplo, cuando se utiliza setMaxPriority() para cambiar la prioridad mxima del grupo, slo est cambiando el atributo en
el grupo, no est cambiando la prioridad de ninguno de los thread del grupo. Consideremos este pequeo programa que crea un
grupo y un thread dentro de l.
class MaxPriorityTest {
public static void main(String[] args) {
El Ejemplo Productor/Consumidor.
El Productor genera un entero entre 0 y 9 (inclusive), lo almacena en un objeto "CubbyHole", e imprime el nmero generado. Para
hacer ms interesante el problema de la sincronizacin, el prodcutor duerme durante un tiempo aleatorio entre 0 y 100 milisegundos
antes de repetir el ciclo de generacin de nmeros.
class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
Productor
El productor extender la clase Thread, y su cdigo es el siguiente:
class Productor extends Thread {
private Tuberia tuberia;
private String alfabeto = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Notar que creamos una instancia de la clase Tuberia, y que se utiliza el mtodo tuberia.lanzar() para que se vaya construyendo la
tubera, en principio de 10 caracteres.
Consumidor
Veamos ahora el cdigo del consumidor, que tambin extender la clase Thread:
class Consumidor extends Thread {
private Tuberia tuberia;
El mtodo notify() al final de cada mtodo de acceso avisa a cualquier proceso que est esperando por el objeto, entonces el
proceso que ha estado esperando intentar acceder de nuevo al objeto. En el mtodo wait() hacemos que el thread se quede a la
espera de que le llegue un notify(), ya sea enviado por el thread o por el sistema.
Ahora que ya tenemos un productor, un consumidor y un objeto compartido, necesitamos una aplicacin que arranque los threads y
que consiga que todos hablen con el mismo objeto que estn compartiendo. Esto es lo que hace el siguiente trozo de cdigo, del
fuente TubTest.java:
class TubTest {
public static void main( String args[] ) {
Tuberia t = new Tuberia();
Productor p = new Productor( t );
Consumidor c = new Consumidor( t );
p.start();
c.start();
}
}
Compilando y ejecutando esta aplicacin, podremos observar nuestro modelo en pleno funcionamiento.
Monitores
Los objetos, como el CubbyHole que son compartidos entre dos threads y cuyo acceso debe ser sincronizado son llamados
condiciones variables. El lenguaje Java permite sincronizar threads alrededor de una condicin variable mediante el uso de
monitores. Los monitores previenen que dos threads accedan simultneamente a la misma variable.
Los mtodos notify() y wait()
En un nivel superior, el ejemplo Productor/Consumidor utiliza los mtodos notify() y wait() del objeto para coordinar la activadad de
los dos threads. El objeto CubyHole utiliza notify() y wait() para asegurarse de que cada valor situado en l por el Productor es
recuperado una vez y slo una por el Consumidor.
El programa Principal
Aqu tienes una pequea aplicacin Java que crea un objeto CubbyHole, un Producer, un Consumer y arranca los dos threads.
class ProducerConsumerTest {
public static void main(String[] args) {
CubbyHole c = new CubbyHole();
Producer p1 = new Producer(c, 1);
Consumer c1 = new Consumer(c, 1);
p1.start();
c1.start();
}
}
La Salida
Aqu tienes la salida del programa ProducerConsumerTest.
Producer #1 pone: 0
Consumidor #1 obtiene: 0
Productor #1 pone: 1
Consumidor #1 obtiene: 1
Productor #1 pone: 2
Consumidor #1 obtiene: 2
Productor #1 pone: 3
Consumidor #1 obtiene: 3
Productor #1 pone: 4
Consumidor #1 obtiene: 4
Productor #1 pone: 5
Consumidor #1 obtiene: 5
Productor #1 pone: 6
Consumidor #1 obtiene: 6
Productor #1 pone: 7
Consumidor #1 obtiene: 7
Productor #1 pone: 8
Consumidor #1 obtiene: 8
Productor #1 pone: 9
Consumidor #1 obtiene: 9
El lenguaje Java y el sistema de ejecucin soportan la sincronizaxin de threads mediante el uso de monitores. En general, un
monitor est asociado con un objeto especifico (una condicin variable) y funciona como un bloqueo para ese dato. Cuando un
thread mantiene el monitor para algn dato del objeto, los otros threads estn bloqueados y no pueden ni inspeccionar ni modificar
el dato.
Los segmentos de cdigo dentro de programa que acceden al mismo dato dentro de threads concurrentes separados son
conocidos como secciones crticas. En el lenguaje Java, se pueden marcar las secciones crticas del programa con la palabra clave
synchronized.
Nota: Generalmente, la seccin crticas en los programas Java son mtodos. Se pueden marcar segmentos pequeos de cdigo
como sincronizados.
Sin embargo, esto viola los paradigmas de la programacin orientada a objetos y produce un cdigo que es dficil de leer y de
mantener. Para la mayora de los propsitos de programacin en Java, es mejor utilizar synchronized slo a nivel de mtodos.
En el lenguaje Java se asocia un nico monitor con cada objeto que tiene un mtodo sincronizado. La clase CubbyHole del ejemplo
Producer/Consumer de la pgina anterior tiene dos mtodos sincronizados: el mtodo put(), que se utiliza para cambiar el valor de
CubbyHole, y el mtodo get(), que se utiliza para el recuperar el valor actual. As el sistema asocia un nico monitor con cada
ejemplar de CubbyHole.
Aqu tienes el cdigo fuente del objeto CubbyHole. Las lneas en negrita proporcionan la sincronizacin de los threads.
class CubbyHole {
private int contents;
private boolean available = false;
El sistema de ejecucin de Java permite que un thread re-adquiera el monitor que ya posee realmente porque los monitores Java
son re-entrantes. Los monitores re-entrantes son importantes porque eliminan la posibilidad de que un slo thread ponga en punto
muerto un monitor que ya posee.
Consideremos esta clase.
class Reentrant {
public synchronized void a() {
b();
System.out.println("Estoy aqu, en a()");
}
public synchronized void b() {
System.out.println("Estoy aqu, en b()");
}
}
Esta clase contiene dos mtodos sincronizados: a() y b(). El primer mtodo sincronizado, llama al otro mtodo sincronizado.
Cuando el control entra en el mtodo a(), el thread actual adquiere el monitor del objeto Reentrant. Ahora, a() llama a b() y como
tambin est sincronizado el thread tambin intenta adquirir el monitor de nuevo. Como Java soporta los monitores re-entrantes,
esto si funciona. El thread actual puede adquirir de nuevo el monitor del objeto Reentrant y se ejecutan los dos mtoso a() y b(),
como evidencia la salida del programa.
Estoy aqu, en b()
Estoy aqu, en a()
En sistemas que no soportan monitores re-entrantes, esta secuencia de llamadas a mtodos causara un punto muerto.
Los mtodos get() y put() del objeto CubbyHole hacen uso de los mtodos notify() y wait() para coordinar la obtencin y puesta de
valores dentro de CubbyHole. Los mtodos notify() y wait() son miembros de la clase java.lang.Object.
Nota:
Los mtodos notify() y wait() pueden ser invocados slo desde dentro de un mtodo sincronizado o dentro de un bloque o una
sentencia sincronizada.
Investiguemos el uso del mtodo notify() en CubbyHole mirando el mtodo get().
El mtodo notify()
El mtodo get() llama al mtodo notify() como lo ltimo que hace (junto retornar). El mtodo notify() elige un thread que est
esperando el monitor poseido por el thread actual y lo despierta. Normalmente, el thread que espera capturar el monitor y
proceder.
El caso del ejemplo Productor/Consumidor, el thread Consumidor llama al mtodo get(), por lo que el mtodo Consumidor posee el
monitor de CubbyHole durante la ejecucin del mtodo get(). Al final del mtodo get(), la llamada al mtodo notify() despierta al
thread Productor que obtiene el monitor de CubbyHole y procede.
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) {
}
}
available = false;
notify(); // lo notifica al Productor
return contents;
}
Si existen varios threads esperando por un monitor, el sistema de ejecucin Java elige uno de esos threads, sin ningn compromiso
ni garanta sobre el thread que ser eligido.
El mtodo put() trabaja de un forma similar a get(), despertanto al thread consumidor que est esperando que el Productor libere el
monitor.
La clase Object tiene otro mtodo --notifyAll()-- que despierta todos lo threads que estn esperando al mismo monitor. En esta
Situacin, los threads despertados compiten por el monitor. Uno de ellos obtiene el monitor y los otros vuelven a esperar.
El mtodo wait()
El mtodo wait() hace que el thread actual espere (posiblemente para siempre) hasta que otro thread se lo notifique o a que cambie
un condicin. Se utiliza el mtodo wait() en conjuncin con el mtodo notify() para coordinar la actividad de varios threads que
utilizan los mismos recursos.
El mtodo get() contiene una sentencia while que hace un bucle hasta que available se convierte en true. Si available es false -- el
Productor todava no ha producido un nuevo nmero y el consumidor debe esperar -- el mtodo get() llama a wait().
El bucle while contiene la llamada a wait(). El mtodo wait() espera indefinidamente hasta que llegue una notificacin del thread
Productor. Cuando el mtodo put() llama a notify(), el Consumidor despierta del estado de espera y contina con el bucle.
Presumiblemente, el Productor ya ha generado un nuevo nmero y el mtodo get() cae al final del bucle y procede. Si el Productor
no ha generado un nuevo nmero, get() vuelve al principio del bucle y continua espeando hasta que el Productor genere un nuevo
nmero y llame a notify().
public synchronized int get() {
while (available == false) {
try {
wait(); // espera una llamada a notify() desde el Productor
} catch (InterruptedException e) {
}
}
available = false;
notify();
return contents;
}
El mtodo put() trabaja de un forma similar, esperando a que el thread Consumidor consuma el valor actual antes de permitir que el
Productor genere uno nuevo.
Junto a la versin utilizada en el ejemplo de Productor/Consumidor, que espera indefinidamente una notificacin, la clase Object
contiene otras dos versiones del mtodo wait().
wait(long timeout)
Espera una notificacin o hasta que haya pasado el tiempo de espera --timeout se mide en milisegundos.
wait(long timeout, int nanos)
Espera una notificacin o hasta que hayan pasado timeout milisegundos mas nanos nanosegundos.
Los Monitores y los Mtodos notify() y wait()
Habras observado un problema potencial en los mtodos put() y get() de CubbyHole. Al principio del mtodo get(), si el valor de
CubbyHole no est disponible (esto es, el Productor no ha generado un nuevo nmero desde la ltima vez que el Consumidor lo
consumi), luego el Consumidor espera a que el Productor ponga un nuevo nmero en CubbyHole. Aqu est la cuestin -- cmo
puede el Productor poner un nuevo valor dentro de CubbyHole si el Consumidor tiene el monitor? (El Consumidor posee el monitor
de CubbyHole porque est dentro del mtodo get() que est sincronizado).
Similarmente, al principio del mtodo put(), si todava no se ha consumido el valor, el Productor espera a que el Consumidor
consuma el valor del CubbyHole. Y de nuevo llegamos a la cuestin -- Cmo puede el consumidor obtener el valor de CubbyHole,
si el Productor posee el monitor? (El productor posee el monitor de CubbyHole porque est dentro dentro del mtodo put() que est
sincronizado).
Bien, los diseadores del lenguaje Java tambin pensaron en esto. Cuando el thread entra en el mtodo wait(), lo que sucede al
principio de los mtodos put() y get, el monitor es liberado automticamente, y cuando el thread sale del mtodo wait(), se adquiere
de nuevo el monitor. Esto le da una oportunidad al objeto que est esperando de adquirir el monitor y, dependiendo, de quin est
esperando, consume el valor de CubbyHole o produce un nuevo valor para el CubbyHole.
16. Desafio.
Basados en el Taller desarrollado en la materia, implementar con Threads una simulacin de busqueda de polen por parte de las
Abejas Obreras.
Las condiciones son las siguientes.
Las Flores que fueron recolectadas no pueden proporcionar mas polen a otra.
Las abejas obreras que no pueden recolectar polen porque otras lo estan haciendo deben buscar otra Flor.
La simulacin termina cuando no haya mas flores con polen que recolectar.