Threads en Java

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 25

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?

Todos los programadores estn familiarizados con la escritura de programas secuenciales.


T probablemente hayas escrito un programa que muestre "Hola Mundo!", o que ordene una lista de nombres, o que calcule la lista
de nmeros primos. Estos son programas secuenciales: cada uno tiene un principio, una secuencia de ejecucin y un final. En un
momento dado durante la ejecucin del programa hay un slo punto de ejecucin.
Un Thread es similar a los programas secuenciales descritos arriba: un slo thread tambin tiene un principio, un final, una
secuencia, y en un momento dado durante el tiempo de ejecucin del thread slo hay un punto de ejecucin. Sin embargo, un
thread por si mismo no es un programa. No puede ejecutarse por s mismo, pero si con un programa.

Definicin:

Un thread es un flujo secuencial de control dentro de un programa.

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.

2) Un Ejemplo Sencillo de un Thread

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 SimpleThread extends Thread {


public SimpleThread(String str) {
super(str);
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i + " " + getName());
try {
sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println("HECHO! " + getName());
}
}
El primer mtodo de esta clase es un constructor que toma una cadena como su nico argumento. Este constructor est
implementado mediante una llamada al consturctor de la superclase y es intresante para nosotros slo porque selecciona el nombre
del Thread, que se usar ms adelante en el programa.
El siguiente mtodo es el mtodo run(). Este mtodo es el corazn de cualquier Thread y donde tiene lugar la accin del Thread. El
mtodo run() de la clase SimpleThread contiene un bucle for que itera diez veces. En cada iteracin el mtodo muestra el nmero
de iteracin y el nombre del Thread, luego espera durante un intervalo aleatorio de hasta 1 segundo. Despus de haber temrinado
el bucle, el mtodo run() imprime "HECHO!" con el nombre del Thread.
La clase TwoThreadsTest proporciona un mtodo main() que crea dos threads SimpleThread: uno llamado "Jamaica" y otro
llamadao "Fiji". (Si no quieres decidir donde ir de vacaciones puedes utilizar este programa para ayudarte a elegir -- ve a la isla cuyo
threads imprima "HECHO!" primero).

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().

Ejemplo: La clase SimpleThread descrita en Un ejemplo sencillo de Thread.


2. Proporcionar una clase que implemente el interface Runnable, tambin definido en el paquete java.lang. Ahora, cuando se
ejemplifique un thread (bien directamente desde la clase Thread o desde una de sus subclases), dale al nuevo thread un
manejador a un objeto de la clase Runnable. Este objeto Runnable proporciona el mtodo run() para el thread.

Ejemplo: El applet de un reloj que vers en la pgina siguiente.


Existen varias buenas razones para elegir uno de estas dos opciones. Sin embargo, para la mayora de los casos, la mejor opcin
ser seguir est regla del pulgar.
Regla del Pulgar:

Si tu clase debe ser una subclase de otra clase (el ejemplo ms comn son lo applets), debers utilizar Runnable.

5) Un Applet de un Reloj Digital


Este applet muestra la hora actual y la actualiza cada segundo. Puedes moverte por la pgina o realizar cualquier otra tarea
mientras el reloj continua actualizandose porque el cdigo que actualiza el reloj y lo muestra se ejecuta dentro de un thread.
En particular, esta pgina descrbie los segmentos de cdigo que implementa el comportamiento del thread del reloj; no describe el
cdigo que est relacionado con el ciclo de vida del applet.

Decidir Utilizar la Interfaz Runnable


El applet del reloj utiliza el interface Runnable para proporcionar el mtodo run() para su thread. Para ejecutarse dentro de un
navegador compatible con Java, la clase Clock debe derivar de la clase Applet. Sin embargo este applet tambin necesita un thread
para poder actualizar continuamente la pantalla sin tomar posesin del proceso en el que se est ejecutando.
(Algunos navegadores, pero no todos, crean un nuevo thread para cada applet para impedir que un applet descorts tome posesin
del thread principal del navegador. Sin embargo, no deberas contar con esto cuando escribas tus applets; los applets deben crear
sus propios threads cuando hagan un trabajo de calculo intensivo). Como el lenguaje Java no soporta la herencia mltiple, la clase
Class no puede heredarse desde la clase Thread y de la clase Applet a la vez.
Por eso, la clase Clock debe utilizar el interface Runnable para proporcionar el comportamiento de sus thread.
Los applets no son threads, ni ningn navegador existente -- compatibles con Java o visualizadoes de applets crean threads
automticante en el que ejecutar applets. Por lo tanto, si un applet, necesita un thread debe crerselo el mismo. El applet del reloj
necesita un thread en el que realizar las actualizaciones de pantalla porque se actualiza de forma frecuente y el usuario necesita
poder realizar otras tareas a la vez que se ejecuta el reloj (como ir a otra pgina o moverse por sta).
La Interfaz Runnable
El applet del reloj proporciona un mtodo run() para su thread mediante el interface Runnable. La definicin de la lcase Clock indica
que es una subclase de la clase Applet y que implementa el interface Runnable. Si no ests familiarizado con los interfaces puedes
revisar la informacin de la leccin "Objetos, Clases, e Interfaces"
class Clock extends Applet implements Runnable {
El interface Runnable define un slo mtodo llamado run() que no acepta ningn argumento y que no devuelve ningn valor. Como
la clase Clock implementa el interface Runnable, debe proporcionar una implementacin para el mtodo run() como est definido
en el interface. Sin embargo, antes de explicar el mtodo run() de la clase Clock, echemos un vistazo a los otros elementos del
cdigo de esta clase.
Crear el Thread
La aplicacin en la que se ejecuta el applet llama al mtodo start() del applet cuando el usuario visita la pgina del applet. El applet
del reloj crea un Thread, clockThread, en su mtodo start() y arranca el thread.
public void start() {
if (clockThread == null) {
clockThread = new Thread(this, "Clock");
clockThread.start();
}
}
Primero el mtodo start() comprueba si clockThread es nulo. Si lo es, significa que el applet acaba de ser cargado o que ha sido
parado anteriormente y se debe crear un nuevo Thread.
De otro modo, significa que el applet ya se est ejecutando. El applet crea un nuevo Thread con esta llamada.
clockThread = new Thread(this, "Clock");
Observa que this -- el applet Clock -- es el primer argumento del constructor del thread.
El primer argumento de este constructor Thread debe implementar el interface Runnable y se convierte en el origen del thread.
Cuando se construye de esta forma, el thread del reloj obtiene su mtodo run() desde el objeto Runnable origen -- en este caso el
applet Clock.
El segundo argumento es slo el nombre del thread.
Parar el Thread
Cuando abandones la pgina que muestra el Reloj, la aplicacin en la que el applet se est ejecutando llama al mtodo stop() del
applet. El mtodo stop() del applet Clock pone clockThread a nulo. Esto le dice al bucle principal en el mtodo run() que termine
(observa la siguiente seccin), la actualizacin del reloj resultando eventualmente en la parada del thread y la recoleccin de
basura.
public void stop() {
clockThread = null;
}
Podras utilizar clockThread.stop() en su lugar, lo que parara inmediatamente el thread del reloj. Sin embargo, el mtodo stop()
de la clase Thread tiene un efecto sbito, lo que significa que el mtodo run() podra estar en medio de una operacin crtica
cuando se pare el thread. Para los mtodos run() ms complejos, utilizar el mtodo stop() de la clase Thread podra dejar el
programa en un estado incosistente. Por esta razn, es mejor evitar el uso del mtodo stop() de la clase Thread cuando sea
posible.
Si revisita la pgina de nuevo, se llama otra vez al mtodo start() y el reloj arranca de nuevo con un nuevo thread.
El Mtodo Run
Y finalmente, el mtodo run() del applet Clock implementa el corazn de este applet y se parace a esto.
public void run() {
// El bucle termina cuando clockThread se pone a null en stop()
while (Thread.currentThread() == clockThread) {
repaint();
try {
clockThread.sleep(1000);
} catch (InterruptedException e){
}
}
}
Como se vi en la seccin anterior, cuando se le pide al applet que se pare, este selecciona clockThread a null; esto permite que el
mtodo run() sepa cuando debe parar.
As, la primera lnea del mtodo run() tiene un bucle hasta que clockThread sea nulo. Dentro del bucle, el applet se repinta a s
mismo y le dice al Thread que espere durante 1 segudo (1000 milisegundos). Luego el mtodo repaint() del applet llama al mtodo
paint() del applet, que actualiza el rea de pantalla del applet.
El mtodo paint() de nuestro applet Clock obtiene la hora actual y la muestra en la pantalla.
public void paint(Graphics g) {
Date now = new Date();
g.drawString(now.getHours() + ":" + now.getMinutes() +
":" + now.getSeconds(), 5, 10);
}

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.

Regla del Pulgar:


En un momento dado, el thread con prioridad superior se est ejecutando. Sin embargo, este no es una garanta. El programador de
threads podra elegir otro thread con prioridad inferior para evitar el hambre. Por esta razn, el uso de las prioridades slo afecta a
la poltica del programador para propsitos de eficiencia. No dependas de la prioridad de los threads para algoritmos incorrectos.

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

Thread #1, tick = 400000


Esto es porque el sistema sin tiempo compartido elige uno de los threads con igual prioridad para ejecutarlo y le permite ejecutarse
hasta que abandone la CPU o hasta que aparezca un thread con prioridad superior.
Nota:
El sistema de ejecucin Java no implementa (y por lo tanto no garantiza) el tiempo compartido. Sin embargo, algunos sistemas en los
que se puede ejecutar Java si soportan el tiempo compartido. Los programas Java no deberan ser relativos al tiempo compartido ya
que podran producir resultados diferentes en distintos sistemas.
Escribir cdigo que haga un uso intensivo de la CPU puede tener repercusiones negativas en otros threads que se ejecutan en el
mismo proceso. En general, se debera intentar escribir threads con "buen comportamiento" que abandonen voluntariamente la
CPU de forma peridica y le den una oportunidad a otros threads para que se ejecuten.
En particular, no escribas nunca cdigo Java que trate con tiempo compartido-- esto garantiza prcticamente que tu programa dar
diferentes resultados en distintos sistemas de ordenador.
Un thread puede abandonar la CPU (sin ir a dormir o algn otro mtodo drstico) con una llamada al mtodo yield(). Este mtodo
da una oportunidad a otros threads con la misma prioridad. Si no existen otros threads con la misma prioridad en el estado
"ejecutable", este mtodo ser ignorado.
Sumario
La mayora de los ordenadores slo tienen una CPU, los threads deben compartir la CPU con otros threads. La ejecucin
de varios threas en un slo CPU, en cualquier orden, se llama programacin. El sistema de ejecucin Java soporta un
algoritmo de programacin determinstico que es conocido como programacin de prioridad fija.
A cada thread Java se le da una prioridad numrica entre MIN_PRIORITY y MAX_PRIORITY (constantes definidas en la
clase Thread). En un momento dato, cuando varios threads estn listos para ejecutarse, el thread con prioridad superior
ser el elegido para su ejecucin.
Slo cuando el thread para o se suspende por alguna razn, se empezar a ejecutar un thread con priporidad inferior.
La programacin de la CPU es totalmente preemptiva. Si un thread con prioridad superior que el que se est ejecutando
actualmente necesita ejecutarse, toma inmediatamente posesin del control sobre la CPU.
El sistema de ejecucin de Java no hace abandonar a un thread el control de la CPU por otro thread con la misma
prioridad. En otras palabras, el sistema de ejecucin de Java no comparte el tiempo. Sin embargo, algunos sistemas si lo
soportan por lo que no se debe escribir cdigo que est relacionado con el tiempo compartido.
Adems, un thread cualquiera, en cualquier momento, puede ceder el control de la CPU llamando al mtodo yield(). Los
threads slo pueden 'prestar' la CPU a otros threads con la misma priorida que l -- intentar cederle la CPU a un thread
con prioridad inferior no tendr ningn efecto.
Cuando todos los threads "ejecutables" del sistema tienen la misma prioridad, el programador elige a uno de ellos en una
especie de orden de competicin.

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.

El Grupo de Threads por Defecto

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.

Crear un Thread en un Grupo Especfico

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.

Obtener el Grupo de un Thread

Para encontrar el grupo en el que est un thread, puede llamar a su mtodo getThreadGroup().
theGroup = miThread.getThreadGroup();

10) La Clase ThreadGroup


La clase ThreadGroup maneja grupos de threads para las aplicaciones Java. Un ThreadGroup puede contener cualquier nmero de
threads. Los threads de un grupo generalmente estn relacionados de alguna forma, como por quin fueron creados, qu funcin
realizan o cundo deben arrancar o parar.
Un threadGroup no slo puede contener threads, tambin puede contener otros ThreadGroups. El grupo principal en una aplicacin
Java es el grupo de threads llamado "main".
Se pueden crear threads y grupos de threads dentro del grupo "main". Tambin se pueden crear threads y grupos de threads dentro
de subgrupos de "main" y as sucesivamente.Esto resulta en una herencia del tipo raiz de los threads y los grupos de threads.
La clase ThreadGroup tiene mtodos que pueden ser categorizados de la siguiente forma.
Mtodos de Manejo de la Coleccin
El ThreadGroup proporciona un juego de mtodos que maneja los threads y los subgrupos dentro de un grupo y permite que otros
objetos le pregunten a ThreadGroup sobre su contenido. Por ejemplo, se puede llamar al mtodo activeCount() para encontrar el
nmero de threads activos actualmente dentro del grupo. Este mtodo se utiliza frecuentemente con el mtodo enumerate() para
obtener un array con las referencias de todos los threads activos en el ThreadGroup. Por ejemplo, el mtodo listCurrentThreads()
del siguiente ejemplo rellena un array con todos los threads activos en el grupo actual e impime sus nombres.
class EnumerateTest {
void listCurrentThreads() {
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
int numThreads;
Thread[] listOfThreads;

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) {

ThreadGroup groupNORM = new ThreadGroup(


"Un grupo con prioridad normal");
Thread priorityMAX = new Thread(groupNORM,
"Un thread con prioridad mxima");

// Selecciona la prioridad del thread al mximo (10)


priorityMAX.setPriority(Thread.MAX_PRIORITY);

// Selecciona la prioridad del grupo a normal (5)


groupNORM.setMaxPriority(Thread.NORM_PRIORITY);

System.out.println("Mxima prioridad del grupo = " +


groupNORM.getMaxPriority());
System.out.println("Prioridad del Thread = " +
priorityMAX.getPriority());
}
}
Cuando se crea el grupo groupNORM hereda su atributo de prioridad mxima desde su grupo padre. En este caso, la prioridad del
grupo padre es la mxima (MAX_PRIORITY) permitida por el sistema de ejecuin de Java. Luego el programa selecciona la
prioridad del thread priorityMAX al mximo permitido por el sistema Java. Luego el programa baja la prioridad del grupo a normal
(NORM_PRIORITY). El mtodo setMaxPriority() no afecta a la prioridad del thread priorityMAX, por eso en este punto, el thread
priorityMAX tienen un prioridad de 10 que es mayor que la prioridad mxima de su grupo groupNORM. Esta es la salida del
programa.
Prioridad mxima del Grupo = 5
Prioridad del Thread = 10
Como se puede ver un thread puede tener una prioridad superior que el mximo permitido por su grupo siempre que la prioridad del
thread se haya seleccionado antes de haber bajado la prioridad mxima del grupo. La prioridad mxima de un grupo de threads se
utiliza para limitar la prioridad de los threads cuando son creados dentro de un grupo o cuando se utiliza setPriority() para cambiar
la prioridad del thread. Observa que setMaxPriority() tambin cambia la prioridad mxima de todos sus subgrupos.
Sin embargo, el estado daemon de un grupo slo se aplica al grupo. Cambiar el estado daemon de un grupo no afecta al estado
daemon de los threads que hay dentro del grupo. Adems, el estado daemon de un grupo no implica de ninguna forma el estado
daemon de sus treads -- se puede poner cualquier thread dentro de un grupo de threads daemon. El daemon de un grupo de
threads slo implica que el grupo puede ser destruido cuando todos los threads se han terminado.
Mtodos que Operan con Todos los Threads de un Grupo.
La clase ThreadGroup tiene tres mtodos que le permiten modificar el estado actual de todos los threads de un grupo.
resume()
stop()
suspend()
Estos mtodos aplican el cambio de estado apropiado a todos los threads del grupo y sus subgrupos.
Mtodos de Restriccin de Acceso
La clase ThreadGroup por si misma no impone ninguna restriccin de acceso, como permitir que los threads de un grupo puedan
inspeccionar o medificar threads de un grupo diferente. Mas bien las clases Thread y ThreadGroup cooperan con los manejadores
de seguridad (subclases de la clase java.lang.SecurityManager), que puede imponer restricciones de acceso basndose en los
miembros de un grupo de threads.
Las clases Thread y threadGroup tienen un mtodo, checkAccess(), que llama al mtodo checkAccess() del controlador de
seguridad actual. El controlador de seguridad decide si permite el acceso basndose en los miembros del grupo de threads
involucrado.
Si el acceso no est permitido, el mtodo checkAccess() lanza una excepcin SecurityException. De otro modo el mtodo
checkAccess() simplemente retorna.
La siguiente lista muestra varios mtodos de ThreadGroup que llaman a checkAccess() antes de realizar la accin del mtodo.
Esto se conoce como acceso regulado, esto es, accesos que deben ser aprobados por el controlador de seguridad antes de poder
ser completados.

ThreadGroup(ThreadGroup padre, String nombre)


setDaemon(boolean isDaemon)
setMaxPriority(int maxPriority)
stop()
suspend()
resume()
destroy()
Esta es una lista de mtodos de la clase Thread que llaman a checkAccess() antes de proceder.
Constructores que especifican un grupo de threads.
stop()
suspend()
resume()
setPriority(int priority)
setName(String name)
setDaemon(boolean isDaemon)
Una aplicacin Java solitaria no tiene un controlador de seguridad por defecto. Esto es, por defecto, no se imponen restricciones a
ningn thread para que pueda inspeccionar o modificar cualquier otro thread, sin importar el grupo en el que se encuetra. Se puede
definir e implementar propias restricciones de acceso para los grupos de threads mediante la subclasificacin de la clase
SecurityManager, sobreescribiendo los mtodos apropiados, e instalandolo como el controlador de seguridad para su aplicacin.
El navegador HotJava es un ejemplo de aplicacin que implementa su propio controlador de seguridad. HotJava necesita
asegurarse de que los applets tengan un buen comportamiento y no hagan cosas sucias a otros applets que se estn ejecutando al
mismo tiempo (como bajar la prioridad de otros threads de otros applets). El controlador de seguridad de HotJava no permite que un
thread modifique threads de otro grupo. Por favor, observa que las restricciones de acceso basadas en los grupos de threads
pueden variar de un navegador a otro y por eso tus applets pueden tener comportamientos diferentes en diferentes navegadores.
11) Programas con Varios Threads
Sincronizacin de Threads
Frecuentemente, los threads necesitan compartir datos. Por ejemplo, supongamos que existe un thread que escribe datos en un
archivo mientras, al mismo tiempo, otro thread est leyendo el mismo archivo.
Cuando los threads comparten informacin necesitan sicronizarse para obtener los resultados deseados.
Imparcialidad, Hambre y Punto Muerto
Si se escribe un programa en el que varios threads concurrentes deben competir por los recursos, se debe tomar las precauciones
necesarias para asegurarse la justicia. Un sistema es justo cuando cada thread obtiene suficiente acceso a los recursos limitados
como para tener un progreso razonable. Un sistema justo previene el hambre y el punto muerto. El hambre ocurre cuando uno o
ms threads de un programa estn bloqueados por ganar el acceso a un recurso y as no pueden progresar. El punto muerto es la
ltima forma de hambre; ocurre cuando dos o ms threads estn esperando una condicin que no puede ser satisfecha. El punto
muerto ocurre muy frecuentemente cuando dos (o ms) threads estn esperando a que el otro u otros haga algo.
Volatile
Los programas pueden modificar variables miembros fuera de la proteccin de un mtodo o un bloque sincronizados y puede
declarar que la variable miembro es volatile.
Si una variable miembro es declarada como volatile, el sistema de ejecucin Java utiliza esta informacin para asegurarse que la
variable sea cargada desde la mmoria antes de cada uso, y almacenada en la memoria despus de utilizarla. Esto asegura que el
valor de la variable es consistente y coherente a lo largo del programa.
12) Sincronizacin de Threads
Las lecciones anteriores contenan ejemplos con threads asncronos e independientes. Esto es, cada thread contena todos los
datos y mtodos necesarios y no requerian recursos externos. Adems, los threads de esos ejemplos se ejecutaban en su propio
espacio sin concernir sobre el estado o actividad de otros threads que se ejecutaban de forma concurrente.
Sin embargo, existen muchas situaciones interesantes donde ejecutar threads concurrentes que compartan datos y deban
considerar el estado y actividad de otros threads. Este conjunto de situaciones de programacin son conocidos como escenarios
'productor/consumidor'; donde el productor genera un canal de datos que es consumido por el consumidor.
Por ejemplo, puedes imaginar una aplicacin Java donde un thread (el productor) escribe datos en un archivo mientras que un
segundo thread (el consumidor) lee los datos del mismo archivo. O si tecleas caracteres en el teclado, el thread productor situa las
pulsaciones en una pila de eventos y el thread consumidor lee los eventos de la misma pila. Estos dos ejemplos utilizan threads
concurrentes que comparten un recurso comn; el primero comparte un archivo y el segundo una pila de eventos.
Como los threads comparten un recurso comn, deben sincronizarse de alguna forma.
Esta leccin ensea la sincronizacin de threads Java mediante un sencillo ejemplo de productor/consumidor.

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;

public Producer(CubbyHole c, int number) {


cubbyhole = c;
this.number = number;
}

public void run() {


for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println("Productor #" + this.number + " pone: " + i);
try {
sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {
}
}
}
}
El Consumidor, estndo hambriento, consume todos los enteros de CubbyHole (exactamenten el mismo objeto en que el productor
puso los enteros en primer lugar) tan rpidamente como estn disponibles.
class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;

public Consumer(CubbyHole c, int number) {


cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
value = cubbyhole.get();
System.out.println("Consumidor #" + this.number + " obtiene: " +
value);
}
}
}
En este ejemplo el Productor y el Consumidor comparten datos a travs de un objeto CubbyHole comn. Observara que ninguno
de los dos hace ningn esfuerzo sea el que sea para asegurarse de que el consumidor obtiene cada valor producido una y slo una
vez. La sincronizacin entre estos dos threads realmente ocurre a un nivel inferior, dentro de los mtodos get() y put() del objeto
CubbyHole. Sin embargo, asumamos por un momento que estos dos threads no estn sincronizados y veamos los problemas
potenciales que podra provocar esta situacin.
Un problema sera cuando el Productor fuera ms rpido que el Consumidor y generara dos nmeros antes de que el Consumidor
tuviera una posibilidad de consumir el primer nmero. As el Consumidor se saltara un nmero. Parte de la salida se podra parecer
a esto.
. . .
Consumidor #1 obtiene: 3
Productor #1 pone: 4
Productor #1 pone: 5
Consumidor #1 obtiene: 5
. . .
Otro problema podra aparecer si el consumidor fuera ms rpido que el Productor y consumiera el mismo valor dos o ms veces.
En esta situacin el Consumidor imprimir el mismo valor dos veces y podra producir una salida como esta.
. . .
Productor #1 pone: 4
Consumidor #1 obtiene: 4
Consumidor #1 obtiene: 4
Productor #1 pone: 5
. . .
De cualquier forma, el resultado es errneo. Se quiere que el consumidor obtenga cada entero producido por el Productor y slo
una vez. Los problemas como los escritos anteriormente,se llaman condiciones de carrera. Se alcanzan cuando varios threads
ejecutados asncronamente intentan acceder a un mismo objeto al mismo tiempo y obtienen resultados errneos.
Para prevenir estas condiciones en nuestro ejemplo Productor/Consumidor, el almacenamiento de un nuevo entero en CubbyHole
por el Productor debe estar sincronizado con la recuperacin del entero por parte del Consumidor. El Consumidor debe consumir
cada entero exactamente una vez. El programa Productor/Consumidor utiliza dos mecanismos diferentes para sincronizar los
threads Producer y Consumer; los monitores, y los mtodos notify() y wait().
Mas ejemplos. Un thread produce una salida, que otro thread usa (consume), sea lo que sea esa salida. Vamos entonces a crear un
productor, que ser un thread que ir sacando caracteres por su salida; crearemos tambin un consumidor que ira recogiendo los
caracteres que vaya sacando el productor y un monitor que controlar el proceso de sincronizacin entre los threads. Funcionar
como una tubera, insertando el productor caracteres en un extremos y leyndolos el consumidor en el otro, con el monitor siendo la
propia tubera.

Productor
El productor extender la clase Thread, y su cdigo es el siguiente:
class Productor extends Thread {
private Tuberia tuberia;
private String alfabeto = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

public Productor( Tuberia t ) {


// Mantiene una copia propia del objeto compartido
tuberia = t;
}

public void run() {


char c;

// Mete 10 letras en la tubera


for( int i=0; i < 10; i++ ) {
c = alfabeto.charAt((int)(Math.random()*26 ));
tuberia.lanzar(c);
// Imprime un registro con lo aadido
System.out.println( "Colocando "+c+" en la tuberia." );
// Esperar un poco antes de aadir ms letras
try {
sleep( (int)(Math.random() * 100 ) );
} catch( InterruptedException e ) {
;
}
}
}
}

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;

public Consumidor( Tuberia t ) {


// Mantiene una copia propia del objeto compartido
tuberia = t;
}

public void run() {


char c;

// Consume 10 letras de la tubera


for( int i=0; i < 10; i++ )
{
c = tuberia.recoger();
// Imprime las letras retiradas
System.out.println( "Recogido el caracter "+c );
// Espera un poco antes de coger ms letras
try {
sleep( (int)(Math.random() * 2000 ) );
} catch( InterruptedException e ) {
;
}
}
}
}
En este caso, como en el del productor, contamos con un mtodo en la clase Tuberia, tuberia.recoger(), para manejar la
informacin.
Monitor
Una vez vistos el productor de la informacin y el consumidor, nos queda por ver qu es lo que hace la clase Tuberia.
Lo que realiza la clase Tuberia, es una funcin de supervisin de las transacciones entre los dos threads, el productor y el
consumidor. Los monitores, en general, son piezas muy importantes de las aplicaciones multithreaded, porque mantienen el flujo de
comunicacin entre los threads.
class Tuberia {
private char buffer[] = new char[6];
private int siguiente = 0;
// Flags para saber el estado del buffer
private boolean estaLlena = false;
private boolean estaVacia = true;

// Mtodo para retirar letras del buffer


public synchronized char recoger() {
// No se puede consumir si el buffer est vaco
while( estaVacia == true )
{
try {
wait(); // Se sale cuando estaVacia cambia a false
} catch( InterruptedException e ) {
;
}
}
// Decrementa la cuenta, ya que va a consumir una letra
siguiente--;
// Comprueba si se retir la ltima letra
if( siguiente == 0 )
estaVacia = true;
// El buffer no puede estar lleno, porque acabamos de consumir
estaLlena = false;
notify();

// Devuelve la letra al thread consumidor


return( buffer[siguiente] );
}

// Mtodo para aadir letras al buffer


public synchronized void lanzar( char c ) {
// Espera hasta que haya sitio para otra letra
while( estaLlena == true ) {
try {
wait(); // Se sale cuando estaLlena cambia a false
} catch( InterruptedException e ) {
;
}
}
// Aade una letra en el primer lugar disponible
buffer[siguiente] = c;
// Cambia al siguiente lugar disponible
siguiente++;
// Comprueba si el buffer est lleno
if( siguiente == 6 )
estaLlena = true;
estaVacia = false;
notify();
}
}
En la clase Tuberia vemos dos caractersticas importantes: los miembros dato (buffer[]) son privados, y los mtodos de
acceso (lanzar() y recoger()) son sincronizados.
Aqu vemos que la variable estaVacia es un semforo, como los de toda la vida. La naturaleza privada de los datos evita que el
productor y el consumidor accedan directamente a stos. Si se permitiese el acceso directo de ambos threads a los datos, se
podran producir problemas; por ejemplo, si el consumidor intenta retirar datos de un buffer vaco, obtendr excepciones
innecesarias, o se bloquear el proceso.
Los mtodos sincronizados de acceso impiden que los productores y consumidores corrompan un objeto compartido. Mientras el
productor est aadiendo una letra a la tubera, el consumidor no la puede retirar y viceversa. Esta sincronizacin es vital para
mantener la integridad de cualquier objeto compartido. No sera lo mismo sincronizar la clase en vez de los mtodos, porque esto
significara que nadie puede acceder a las variables de la clase en paralelo, mientras que al sincronizar los mtodos, s pueden
acceder a todas las variables que estn fuera de los mtodos que pertenecen a la clase.
Se pueden sincronizar incluso variables, para realizar alguna accin determinada sobre ellas, por ejemplo:
sincronized( p ) {
// aqu se colocara el cdigo
// los threads que estn intentando acceder a p se pararn
// y generarn una InterruptedException
}

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

13) Monitores Java

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;

public synchronized int get() {


while (available == false) {
try {
wait();
} catch (InterruptedException e) {
}
}
available = false;
notify();
return contents;
}

public synchronized void put(int value) {


while (available == true) {
try {
wait();
} catch (InterruptedException e) {
}
}
contents = value;
available = true;
notify();
}
}
La clase CubbyHole tiene dos variables privadas: contents, que es el contenido actual de CubbyHole, y la variable booleana
available, que indica si se puede recuperar el contenido de CubbyHole. Cuando available es verdadera indica que el Productor ha
puesto un nuevo valor en CubbyHole y que el Consumidor todava no la ha consumido. El Consumidor slo puede consumir el valor
de CubbyHole cuando available es verdadera.
Como CubbyHole tiene dos mtodos sincronizados, java proporciona un nico monitor para cada ejemplar de CubbyHole
(incluyendo el compartido por el Productor y el Consumidor). Siempre que el control entra en un mtodo sincronizado, el thread que
ha llamado el mtodo adquiere el monitor del objeto cuyo mtodo ha sido llamado. Otros threads no pueden llamar a un mtodo
sincronizado del mismo objeto hasta que el monitor sea liberado.
Nota:
Los Monitores Java son Re-entrantes.
Es decir, el mismo thread puede llamar a un mtodo sincronizado de un objeto para el que ya tiene el monitor, es decir, puede re-
adquirir el monitor.
As, siempre que el Productor llama al mtodo put() de CubbyHole, adquiere el monitor del objeto CubbyHole, y as evita que el
consumidor pueda llamar al mtodo get() de CubbyHole. (El mtodo wait() libera temporalmente el monitor como se ver ms
adelante).
public synchronized void put(int value) {
// El productor adquiere el monitor
while (available == true) {
try {
wait();
} catch (InterruptedException e) {
}
}
contents = value;
available = true;
notify();
// El productor libera el monitor
}
Cuando el mtodo put() retorna, el Productor libera el monitor y por lo tanto desbloquea el objeto CubbyHole.
Siempre que el Consumidor llama al mtodo get() de CubbyHole, adquiere el monitor de ese objeto y por lo tanto evita que el
productor pueda llamar al mtodo put().
public synchronized int get() {
// El consumidor adquier el monitor
while (available == false) {
try {
wait();
} catch (InterruptedException e) {
}
}
available = false;
notify();
return contents;
// el Consumidor libera el monitor
}
La adquisicin y liberacin del monitor la hace automticamente el sistema de ejecucin de Java. Esto asegura que no puedan
ocurrir condiciones de competicin en la implementacin de los threads, asegurando la integridad de los datos.
Prueba esto: Elimina las lneas que estn en negrita en el listado de la clase CubbyHole mostrada arriba. Recompila el programa y
ejecutalo de nuevo. Qu sucede? Como no se ha realizado ningn esfuerzo explcito para sicronizar los threads, el Consumidor
consume con un abandono temerario y obtiene slo una ristra de ceros, en lugar de obtener los enteros entre 0 y 9 exactamente
una vez cada uno.

14) Los Monitores Java son Re-Entrantes

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.

15) Los Mtodos Wait() y Notify()

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.

Simular un campo de flores como una coleccin de objetos Flores.

Solo puede haber una Abeja Obrera por Flor.

Cada Abeja Obrera puede recolectar polen de mas de una Flor.

Las Flores que fueron recolectadas no pueden proporcionar mas polen a otra.

El tiempo de obtencion del polen por parte de cada abeja es de 2 segundos.

Las abejas obreras que no pueden recolectar polen porque otras lo estan haciendo deben buscar otra Flor.

Si llegan a una Flor que no tiene polen, deben buscar otra.

La simulacin termina cuando no haya mas flores con polen que recolectar.

Mostrar cuantas abejas pudieron recolectar polen.

Mostrar cuantas abejas no pudieron recolectar polen.

También podría gustarte