Vous êtes sur la page 1sur 47

2.- Creacin y uso de threads.

Java threads. Scott Oaks & Henry Wong. Ed. O'Reilly, QA 76.73
.J38 O25 1997.

El uso de "threads" permite que el applet funcione sin interferir en


otros applets.

La clase OurClass tiene un mtodo run que imprimir 100 veces


"Hola".

public class OurClass


{
public void run()
{
for (int i=0; i<100; i++)
System.out.println("Hola!");
}
}
!

Si lo ejecutamos el mtodo run en un applet se obtiene:

import java.applet.Applet;
public class OurApplet extends Applet
{
public void init()
{
OurClass oc = new OurClass();
oc.run();
}
}

Ejecucin run()
Applet ejecuta run()
Applet ejecuta init()
Applet antes de init()
Tiempo

Si sustituimos el mtodo run() por el mtodo start()


este crea un nuevo clase y ejecuta el mtodo run().

import java.applet.Applet;
public class OurApplet extends Applet
{
public void init()
{
OurClass oc = new OurClass();
oc.start();
}
}

Ejecucin run()
Thread ejecuta run()

Applet ejecuta start()


Applet ejecuta init()
Applet antes de init()
Tiempo

!
!

!
!

run(): El mtodo run() contendr el cdigo que se ejecuta.


start(): Crea un nueva clase y ejecuta el mtodo run de la
clase.
static void sleep (long milisegundos).
static void sleep (long milisegundos, int nanosegundos).
Detiene la ejecucin durante un tiempo.

Interface Runnable
!

El interface Runnable contiene un nico mtodo run.

Se puede implementar directamente Runnable sin heredar de


Thread

public class OurClass implements Runnable


{
public void run()
{
for (int i=0; i<100; i++)
System.out.println("Hola!");
}
}
!

Puesto que Outclass no es un Thread (no hereda de Thread)


hemos de hacer cambios al applet:

import java.applet.Applet;
public class OurApplet extends Applet
{
public void init()
{
Runnable ot = new OurClass();
Thread th = new Thread(ot);
th.start();
}
}
!

Se crea el thread utilizando el constructor Thread (Runnable


target) de la clase Thread, el cual crea un nuevo Thread
asociado al Runnable dado.

Ej: Parpadeo en animaciones:


!

Incluir un soporte de interface Runnable proporciona el


comportamiento en el applet para implementar un thread.

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
public class HolaMundoSwirl extends
java.applet.Applet implements Runnable{
Font f = new Font("TimesRoman",Font.BOLD,48);
Color colors[] = new Color[50];
Thread runThread;
public void start(){
if(runThread == null){
runThread = new Thread(this);
runThread.start();
}
}
public void stop(){
if(runThread != null){
runThread.stop();
runThread = null;
}
}
public void run(){
float c =0;
for (int i=0;i<color.length;i++){
colors[i] = Color.getHSBColor(c,
(float)1.0,(float)1.0),(float)1.0);
c+=0.02;
}
int i = 0;
while (true){
setForeground(colors[i];
repaint(); // redibuja el rea
i++;

try{
Thread.currentThread().sleep(50);}
catch (InterruptedException e){ }
if (i == (colors.length)) i = 0;
}
}
public void paint(Graphics g){
g.setFont(f);
g.drawString("Hola Mundo!!!",15,50);
}
}
!

En este caso se transforma el propio applet en un thread.

No obstante cuando trabajemos con applets, no hay que


confundir el mtodo start() del applet con el mtodo
thread.start() del thread.

Mtodo isActive(): determina si el apple est activo o no.


Por definicin un applet est activo entre los mtodos start y stop
del applet.

public void run()


{
while (isActive())
{
try{
repaint();
Thread.sleep(1000);
} catch (Exception e) {}
}
timer = null;
}

Lectura Asncrona
!

Aparece un problema cuando leemos o escribimos en un fichero


o en un socket, ya que esta accin depende de otros recursos ya
sean programas o hardware.

Estos recursos pueden no estar disponibles temporalmente. As


mismo, no existe en el API de Java dispositivos que comprueben
esto.

El uso de threads pueden bloquearse esperando datos mientras


que la ejecucin del applet no es alterada.

Lectura de Sockets.

import java.io.*;
import java.net.*;
public class AsyncReadSocket extends Thread
{
private Socket s;
private StringBuffer result;
public AsyncReadSocket( Socket s)
{
this.s = s;
result = new StringBuffer();
}
public void run()
{
DataInputStream is = null;
try
{
is = new
DataInputStream(s.getInputStream());
} catch (Exception e) {}
while (true)
{
try
{
char c=is.readChar();
result.append;
} catch (Exception e) {}
}
}

public String getResult()


{
String retval = result.toString();
result = new StringBuffer();
return retval;
}
}
!

El mtodo run de este thread lee caracteres desde un socket


almacenndolo en un StringBuffer.

El applet llamara a getResult para obtener cualquier dato que


se halla leido, devolviendo un String vacio si no hay datos.

Posibles colisiones cuando el applet llama a getResult al


mismo tiempo que readChar de vuelve un dato, accediendo
ambos a la instancia result.

Veamos una posible ejecucin.

Thread Bloqueado

Thread lee un dato

Applet llama a
getResult()
Applet ejecutando
otras tareas
Tiempo

1.
2.
3.
4.
5.

Secuencia de eventos:
El applet entra en getResult()
Es asignado a retval el valor del StringBuffer.
El Thread del socket finaliza la ejecucin de readChar
El Thread del socket aade el char ledo al StringBuffer.
El applet asigna a result un StringBuffer vaco (con new).

Estados de un Thread
Periodo durante start()

Periodo durante stop()

Running

Not Running

La clase Thread provee el mtodo isAlive(), para saber si un


thread esta ejecutndose o no. Este est vivo algo antes el de
finalizar el start(), hasta algo despus de finalizar stop().

Se usa tambin para determinar si el mtodo run se ha


completado.

Por ejemplo, en la clase AsyncReadSocket pueden llegar datos


despus de haber sido llamado el mtodo stop().

Mtodo stop(): Se utiliza para parar un thread.

Ejemplo de start() y stop()


import java.applet.Applet;
public class MyApplet extends Applet
{
Thread t;
Public void start()
{
t = new TimerThread(this,500);
t.start();
}

public void stop()


{
t.stop();
while (t.isAlive())
{
try
{
Thread.sleep(100);
} catch (InterrumptedException e){}
}
}
}
!

Mtodo suspend(): Se utiliza para suspender temporalmente


un thread.

Mtodo resumen(): Se utiliza para revivir una tarea


suspendida. Suele llamarse despus de suspend() para que la
tarea vuelva a ejecutarse.

Ejemplo:
Public class
Runnable{
Thread t;
....

java1005

extends

Applet

implements

class ProcesoRaton extends MouseAdapter{


Boolean suspendido;
Public void mousePressed( MouseEvent evt){
If(suspendido) t.resume();
else t.suspend();
Suspendido = !suspendido;
}
}

Mtodo join(): Espera la finalizacin de un thread. Por


definicin join regresa tan pronto como el thread se considera
"not alive".

joint(long timeout): Espera la finalizacin de un thread


durante un tiempo determinado.

joint(long timeout, int nanos): Igual que el anterior


pero con nanosegundos.

Ejemplo de joint()
public class MyApplet extends Applet
{
Thread t;
Public void start()
{
t = new TimerThread(this,500);
t.start();
}
public void stop()
{
t.stop();
try
{
t.join();
} catch (InterrumptedException e){}
}

currentThread(): devuelve el objeto thread que representa


el tarea que se est ejecutando actualmente.

yield(): Hace que el intrprete cambie de contexto entre la


tarea actual y la siguiente tarea ejecutable. Es una manera de
asegurar que las tareas de menor prioridad no sufran inanicin.

static int enumerate(Thread threadArray[]):


Obtiene todos los threads en ejecucin y los almacena en un
array. Devuelve el nmero de threads almacenados en el array.

static int activeCount(): Devuelve el nmero de


threads en el programa.

Ejemplo de enumerate() y activeCount():


import java.applet.*;
import java.awt.*;
public class Animate extends Applet{
public void printThreads(){
Thread ta[]= new Thread[Thread.activeCount()];
int n = Thread.enumerate(ta);
for (int i=0;i<n;i++){
System.out.println("Thread"
ta[i].getName());
}

+"is"

}
}
Una tarea se puede morir de dos formas: por causas naturales
(finaliza la accin del mtodo run()), o matndola.
Ejemplo.

Thread MiThread = new MiClaseThread();


MiThread.start();
try{
MiThread.sleep(10000);
}catch(InterruptedException e){
e.printStackTrace();
}
MiThread.stop();
!

El mtodo stop() enva un objeto ThreadDeath a la tarea que


quiere detener muriendo en el momento que llega ese objeto.

setName(): Asigna un nombre a una instancia de un thread.

getName(): Obtiene el nombre de una instancia del thread


actual.

Ejemplo de setName()
import java.awt.*;
public class TimerThread extends Thread{
Component comp; // componente a repintar
int timediff;
// tiempo entre repintado
public TimerThread(Component comp,int timediff){
this.comp=comp;
this.timediff=timediff;
setName("TimerThread("
+
timediff
+
"milliseconds)");
}
public void run(){
while (true){
try{
comp.repaint();
sleep(timediff);
}catch(Exception e){}
}
}
}
! En esta versin de la claseTimerThread, asignamos un nombre
al thread en el constructor.
!

El nombre es TimerThread + milisegundos transcurridos.


Posteriormente puede llamarse a getName().

El uso de nombres almacena informacin extra que puede


usarse para la depuracin del programa y identificacin de los
distintos threads del programa.

Si queremos saber el thread que est en estos momentos en


ejecucin solo tenemos que insertar un sentencia como la
siguiente:

String reader = Thread.currentThread().getName();

3. Sincronizacin.
!

Los threads son independientes, contienen todos los datos y


mtodos que necesitan para su ejecucin y no necesitan ningn
mtodo exterior.

Solucionar el problema de acceso a variables compartidas.

Ejemplo 1: cuenta bancaria.

1) comprobar que el usuario tiene suficiente dinero en la cuenta


como indica. Si no lo tiene ir al paso 4.
2) Sacar el dinero de la cuenta.
3) Dar el dinero al usuario.
4) Imprimir el recibo.

Public class AutomatedTellerMachine extens Teller


{
public void withdraw( float amount)
{
Account a = getAccount();
if (a.deduct(amount))
dispense(amount);
printReceipt();
}
}
public class Account
{
private float total;
public boolean deduct(float f)
{
if (t <= total)
{
total -= t;
return true;
}
return false;
}
}

Supongamos dos threads A y B accediendo a la misma cuenta.

1)
2)
3)
4)
5)
6)

A comienza la ejecucin del mtodo deduct.


A confirma que tiene dinero. (if t <= total)
B comienza la ejecucin del mtodo deduct.
B confirma que tiene suficiente dinero.
B extrae el dinero (total -= t)
A extrae el dinero.

Mecanismo de sincronizacin para que B se espere hasta que A


termine su ejecucin sobre deduct.

public class Account


{
private float total;
public synchronized boolean deduct(float f)
{
if (t <= total)
{
total -= t;
return true;
}
return false;
}
}

Ejemplo 2: revisin de AsyncReadSocket

public void run()


{
DataInputStream is = null;
try
{
is = new
DataInputStream(s.getInputStream());
} catch (Exception e) {}
while (true)
{
try
{
char c=is.readChar();
appendResult(c);
} catch (Exception e) {}
}
}
public synchronized String getResult(){
String retval = result.toString();
result = new StringBuffer();
return retval;
}
public synchronized void appendResult(char c)
{
result.append(c);
}
}
!

Con la sincronizacin podemos serializar la ejecucin de


mtodos completos y bloques de cdigo.

Una clase Flag para sincronizar.


Es realmente necesario el uso de la
syncronized? Puede usarse un flag?
Ejemplo
public class BusyFlag
{
private Thread busyflag = null;
!

palabra

clave

public void getBusyFlag()


{
while (busyflag != Thread.currentThread())
{
if (busyflag == null)
busyflag= Thread.currentThread();
try {
Thread.sleep(100);
} catch (Exception e){}
}
}
public void freeBusyFlag()
{
if (busyflag == Thread.currentThread())
busyflag = null;
}
}
!

El mtodo getBusyflag se queda en un bucle hasta poner


busyflag al thread actual esperando 100 milisegundos a
continuacin.

El mtodo freeBusyFlag() libera el flag. Esta espera de 100


milisegundos asegura que la operacin se ha terminado.

En un sistema de 2 threads A y B ocurre el siguiente problema:


1) Thread A detecta que busyflag esta libre.
2) Thread B detecta que busyflag esta libre.
3) B se apodera de busyflag.
4) B espera 100 ms
5) B sale confirmando que tiene el flag
6) A se apodera del flag, espera 100ms, sale y confirma que tiene el
flag.
!

Podemos usar esta clase en nuestra clase Account de cuenta


bancaria:

public class Account


{
private float total;
private flag = new BusyFlag();
public boolean deduct(float f)
{
boolean succed= false;
flag.setBusyFlag();
if (t <= total)
{
total -= t;
succed = true;
}
return false;
flag.freeBusyFlag();
return succed;
}
}
!

En la mayoria de los casos este ejemplo funciona, pero no


podemos asegurar que lo har siempre. Por esos se utiliza
synchronized.

Unicamente nos interesa bloquear el objeto durante el periodo de


acceso a los datos. Ejemplo de getBusyFlag:
public void getBusyFlag()
{
while (true)
{
synchronized (this)
{
if (busyflag == null)
{
busyflag= Thread.currentThread();
break;
}
}
try {
Thread.sleep(100);
} catch (Exception e){}
}
!

}
public synchronized void freeBusyFlag()
{
if(getBusyFlagOwner()==Thread.currentThread())
{
busyflag = null;
}
}
!

Mtodos wait() y notify(): Al pertenecer a la clase


Object, cualquier objeto hereda estos mtodos.

wait(): En un bloque sincronizado, el thread se bloquea y


entra en estado de espera hasta que se produzca un evento que
le permita continuar.

public final void wait(long timeout) throws


InterruptedException: El thread espera hasta que es
notificado o se agota el tiempo en milisegundos.

public
final
void
wait()
throws
InterruptedException: Si el timeout es cero el thread
esperar a la notificacin.

public final void wait(long timeout, int nanos)


throws InterruptedException: Se especifica en
nanosegundos.

Mtodo notify(): Despertar aquel thread que lleve ms


tiempo en estado de espera. Dicho thread continua la ejecucin
en el punto en que se bloque.

Mtodo notifyAll(): Si queremos despertar todos los


threads que estn esperando.

Ejemplo:
import java.util.*;
import java.io.*;
public class Consumer extends Thread{
protected Vector objects; // array de objetos
public Consumer(){
objects = new Vector();
}
public void run(){
while (true){
Object object = extract();//saca objeto
System.out.println(object);
}
}
protected Object extract(){
synchronized (objects){ //bloqueamos array
while (objects.isEmpty()){
try{
objects.wait();
}catch (InterruptedException ex){
ex.printStackTrace();
}
Object o =objects.firstElement();
objects.removeElement (o);
return o;
}
}
public void insert (Object o){
synchronized(objects){
objects.addElement (o);
objects.notify();
}
}

public static void main (String args[])


throws IOException{
Consumer c = new Consumer();
BufferedReader i = new
BufferedReader(new InputStreamRead);
c.start;
String s;
while ((s = i.readLine ()) !=null){
c.insert (s);
Thread.sleep(1000);
}
}
}

Gestin de threads
!

Cuando se crea un thread, se le asigna una prioridad (la hereda).

El dispositivo que decide el orden de ejecucin en "tiempo real"


de los threads se denomina scheduler.

Existe diferentes maneras de realizar la gestin de los threads, y


en Java no existe una uniformidad de criterios a la hora de
realizar esta gestin.

Ejemplo: Gestin de la CPU.


class TestThread extends Thread{
String id;
public TestThread(String s){
id = s;
}
public void run(){
int i;
for(i=0;i<10;i++){
doCalc(i);
System.out.println(id);
}
}
}
!

public class Test{


public static void main(String args[]){
TestThread t1,t2,t3;
t1 = new TestThread(Thread 1);
t1.start();
t2 = new TestThread(Thread 2);
t2.start();
t3 = new TestThread(Thread 3);
t3.start();
}
}
!

Asumimos que doCalc()es un mtodo computacionalmente


caro y requiere 5 segundos por llamada. Esto da lugar a la
necesidad de la gestin de los mismos.

Todo thread en la maquina virtual ofrece estos cuatro estados:


o Initial: Est en estado inicial cuando es creado, esto es, si
el constructor es llamado bajo start().
o Runnable: Un thread est en estado runnable una vez el
mtodo start() ha sido llamado.
o Blocket: Esto ocurre cuando el thread no est ejecutndose
pero est esperando para que un evento especfico ocurra.
o Exiting: Un thread esta en estod de existencia cuando el
mtodo run() retorna o stop() es llamado.

Mediante la mquina virtual solo podemos ejecutar un solo


thread a la vez, aunque es frecuente que el programa de Java
tenga ms de un thread en estado runnable.

Cuando esto sucede la mquina virtual selecciona entre el


conjunto uno para ejecutar. El resto permanece en estado
runnable hasta que se produzca un cambio en el mtodo run()
del thread en ejecucin.

La clave est en cual de los threads en estado runnable es


seleccionado para comenzar a ejecutarse.

Para ello, Java establece un orden de prioridades asignando un


numero positivo de rango definido. Esta prioridad solo puede ser
cambiada por el programador.

ATENCIN: La mquina virtual nunca cambia la prioridad de un


thread, cuando cambia de estado o despus de que el thread
halla sido ejecutado durante un cierto periodo de tiempo.

Ej: Un thread de prioridad 5 mantiene esa prioridad desde el


tiempo de creacin con varios cambios de estado entre runnable
y blocket hasta que el thread termina o entra en estado exiting.

Esto garantiza que el thread que esta en ejecucin, es aquel que


mayor prioridad tiene. As, la maquina virtual detiene la ejecucin
de un thread de baja prioridad ante la entrada en runtime de otro
de ms alta cogiendo este ltimo.

setPriority(int): Asigna a la tarea la prioridad indicada


por el valor pasado como parmetro.
!

MIN_PRIORITY,NORM_PRIORITY y MAX_PRIORITY que


toman los valores 1,5,10 respectivamente.

Usaremos NORM_PRIORITY para procesos a nivel de usuario,


MIN_PRIORITY para entrada/salida en red y redibujar en
pantalla.

Con las tareas en las que se fije MAX_PRIORITY, hay que tener
cuidado, ya que si no se hacen llamadas sleep() o yield(),
se puede provocar que el intrprete Java quede fuera de control.

get_Priority(): Devuelve la prioridad de la tarea en curso,


que es un valor comprendido entre 1 y 10.

Ejemplo incompleto de manejo de socket:


public class SchedulingExample implements
Runnable{
public static void main(String args[]){
Thread calcThread = new Thread (this);
calcThread.setPriority(4);
calcThread.start();
AsyncReadSocket reader;
reader = new AsyncReadSocket (new Socket(host,
port));
reader.setPriority(6);
reader.start();
doDefault();
}
public void run(){
doCalc();
}
}
!

Este programa tiene tres threads: primero, hay por defecto un


thread que ejecuta el mtodo main(), el cual despus de
crearse otros threads va ha ejecutar doDefault(). Segundo
hay un thread de calculo calcThread que va ha ejecutar
doCalc(). Por ltimo, el lector AsyncReadSocket reader
est leyendo un socket.

En T1 se ejecuta main() con prioridad NORM_PRIORITY=5 y es


el nico activo en la maquina virtual.

En T2 se crea calcThread con prioridad 4 y llama a start().


Ahora hay dos threads en estado runnable pero al ser inferior
al primero se queda en espera en la CPU.

El thread por defecto main() continua su ejecucin creando el


thread reader con prioridad 6 que llama a start(). Como
ahora reader tiene mayor prioridad que el thread por defecto,
reader comienza a ejecutarse y el thread de main() y
calcThread se queda en espera. Llamaremos a este instante
T3.

Ahora el thread reader ejecuta readChar() dentro del mtodo


run() de la clase AsyncReadSocket. Si no existe ningn dato
reader entra en estado blocket y se el thread de main()
continua la ejecucin (instante T4) en el estado en que se haba
quedado hasta que la entrada del dato de readChar() queda
satisfecha.

En ese instante T5 pasa el thread reader a estado runnable y


como tiene mxima prioridad continua ejecutndose.

Mientras calcThread ha estado pacientemente esperando


para cambiar a run y debe continuar esperando hasta el thread
de main() y el thread reader pasen a estado bloquet o exiting

Puede ocurrir que este thread nunca llegue a ejecutarse


producindose un CPU starvation. Es responsabilidad de
Java developer, asegurarse que ningun thread este en estado
starve.

La mquina virtual de Java, nunca ajustar la prioridad del thread


para compensar la perdida de desarrollo de la CPU.

Threads con igual prioridad


!

Hay en la maquina virtual de Java 10 niveles de prioridad que


conciernen por tanto a 13 listas de posiciones:
o Uno de todos aquellos en el estado inicial.
o Uno de todos aquellos en el estado blocked.
o Uno de todos aquellos en el estado exiting.
o Uno por cada nivel de prioridad.

Si un thread en estado runnable con prioridad 7 esta en la


posicin nmero 7, pero cuando el thread se bloquea, es movido
a la lista blocked.

En Java no existe un ordenamiento cuando varios threads estn


bloqueados.

El sistema de bsqueda es el siguiente:


o Comienza por mirar la lista con threads de prioridad 10. Si
esta vaca, pasa a los de prioridad 9 hasta encontrar un
posicin de la lista no vaca.
o A continuacin, coge el primer elemento de la lista y lo
ejecuta y mueve tambin ese thread al final de la lista.

Volvamos al ejemplo anterior y supongamos que calcThread


tiene la misma prioridad que el thread por defecto main().

En este caso en la posicin 5 tendremos la lista con los threads


main() y calcThread de prioridad 5, mientras que reader
estar en la lista de prioridad 6.

En T2
o PRIORITY 5: main() -> calcThread ->NULL
o PRIORITY 6: NULL
o BLOCKED:NULL

Si la mquina virtual selecciona main() para ejecutar ya que


esta el primero entonces:
o PRIORITY 5: calcThread ->main()->NULL

En T3 comienza reader
o PRIORITY 5: calcThread->main() ->NULL
o PRIORITY 6: reader->NULL
o BLOCKED:NULL
En T4 reader se bloque en espera de datos. Y el primero ahora
es calcThread y se ejecuta.
o PRIORITY 5: main() -> calcThread ->NULL
o PRIORITY 6: NULL
o BLOCKED: reader->NULL
Hasta aqu, el proceso es determinista, pero pueden ocurrir
complicaciones que afecten al gestor de threads de igual
prioridad. Veamos algunos casos:
a. Cuando el thread en ejecucin salga del estado
runnable, bien bloquendose o saliendo. En nuestro
ejemplo esto ocurre cada vez que se bloquea reader.
b. Cuando un thread que tiene una prioridad ms alta que
el thread en ejecucin entra en estado runnable. En
nuestro ejemplo esto ocurre cuando reader entra en
accin o se desbloquea.
c. Cuando se produce una expiracin en un tiempo
arbitrario, Ej: se fija intervalos fijos de 10 veces por
segundo donde hay slo dos threads con prioridad 5:
! PRIORITY 5: main() -> calcThread ->NULL
! PRIORITY 6: NULL
! BLOCKED: reader->NULL
calcThread es el thread en ejecucin. Entonces,
cuando uno de esos intervalos de tiempo cumple se
producen el cambio.
! PRIORITY 5: calcThread->main() ->NULL
! PRIORITY 6: NULL
! BLOCKED: reader->NULL
As, sucesivamente cada decima de segundo.

Si en un momento del programa cambiamos reader a prioridad 5


tendremos:
o PRIORITY 5: reader->calcThread->main()->NULL

En muchas implementaciones UNIX de Java, el sistema es muy


determinista y el thread esta ejecutndose hasta que finaliza su
mtodo run() y sale del estado runnable o hasta que otro thread
con ms alta prioridad entra en estado runnable.

Estados con la misma prioridad no comienzan a ejecutarse y no


se dan situaciones del tipo c.

Sin embargo estas situaciones de tipo c si pueden ocurrir en


Windows NT o Windows 95. Esto parece que pone en cuestin el
sentido de multiplataforma de Java.

Estos diferentes mecanismos de gestin de Java est en relacin


con la gestin de threads con que acta el sistema operativo.

Ejemplo con getPriority y setPriority:


public class Fractal extends Applet implements
Runnable{
Thread calcThread;
public void init(){
Thread current=Thread.currentThread();
calcThread=new Thread(this);
calcThread=setPriority(current.getPriority
()-1);
}
public void start(){
calcThread.start();
}
public void stop(){
calcThread.stop();
}
public void run(){
while(sectionsToCalculate){
doCalc();
repaint();
}
}
public void paint(Graphics g){
//pintar cosas basadas en la ltima
seccin del ltimo fractal calculado
}
}

Supongamos que no asignaramos a calcThread un orden de


prioridad ms bajo. En ese caso, el applet debera ejecutarse
mediante los mtodos init() y start() con 2 threads en
estado NORM_PRIORITY (el del applet y calcThread).

El thread del applet esta en espera a un evento del sistema de


ventana y el thread calcThread es el nico de los theads en
estado runnable que se ejecutar el fractal llamando a
continuacin a repaint(). Esto crea un evento necesario para
desbloquear el thread del applet, el cual pasa a ejecutarse.

Como ambos threads tienen la misma prioridad, en muchas


plataformas UNIX, el applet debe dejar que finalice calcThread
aunque este seguir en estado runnable.

Esto puede producir que si el tiempo de ejecucin para calcular


el fractal es superior a la respuesta del sistema de ventana en
mucha llamadas el mtodo repaint() no tenga efecto ya que
el thread del applet no tiene oportunidad de desbloquearse y
entrar en ejecucin.

void suspend(): Previene que un thread este ejecutndose


indefinidamente en el tiempo. Pasa el thread de estado runnable
a estado bloquet.

void resume(): Permite que un thread reanude su ejecucin


despues de haber sido suspendida. Ejemplo:

public class Fractal extends Applet implements


Runnable{
Thread t;
public void init(){
t=new Thread(this);
}
public void start(){
t.start();
}
public void stop(){
t.stop();
}

public void run(){


while (sectionsToCalculate){
doCalc();
repaint();
t.suspend();
}
}
public void paint(Graphics g){
//Draw the section
t.resume();
}
}
!

La suspensin se produce despus de que cada seccin es


calculada, dando la oportunidad al applet a que comience a
ejecutar la parte grfica.

Hay dos problemas entre cdigo.


!

Quizs quisiramos permitir control sobre el programa. Ej:


que el usuario quisiera cambiar los parmetros grficos a
travs de un interfaz. No podemos permitir que el usuario
realice esto mientras funciona doCalc().

El otro problema es que mientras que el thread de calculo est


en estado runnable, el applet est en estado bloqueado y en
esta situacin no puede leer ningn nuevo valor que le
introduzcamos.

En estado doCalc(), calculation est en estado runnable y el


applet bloqueado.
PRIORITY 5: Calculation ->NULL
BLOQUET:
applet -> NULL
!

Cuando llamamos a repaint() el applet sale del estado


bloqueado y pasa al estado runnable.
PRIORITY 5: Calculation ->Applet->NULL
BLOQUET:
NULL
!

Despus de repintar la nueva seccin del fractal se llama a


resume() dentro de paint. Normalmente resume() manda a

calculation de estado bloquet a estado runnable, pero como


todava no ha sido suspendido no tiene efecto esta llamada.
El applet ante esta situacin pasa de nuevo a estado bloqueado
con lo que no se pintan los cambios realizados.
PRIORITY 5: Calculation ->NULL
BLOQUET:
applet->NULL
!

Posteriormente se llama a suspend() con lo que calculation


tambin se bloquea.
PRIORITY 5: NULL
BLOQUET:
applet->Calculation->NULL
!

Esto ocurre porque los dos threads tienen la misma prioridad y


ambos estn en estado runnable. Veamos una mejor solucin del
problema:
public class Fractal extends Applet implements
Runnable{
Thread t;
!

public void start(){


if (t==null){
t=new Thread(this);
t.setPriority(Thread.currentThread().getPr
iority()-1);
t.start();
}
}
public void stop(){
t.suspend();
}
public void run(){
// calculos y repintado doCalc() y repaint()
}
public void paint(Graphics g){
//Draw the section
}
}
!

Cuando el usuario visita la pagina con el applet, el applet puede


salvar la informacin del fractal ya que slo el thread con ms
alta prioridad es el responsable de suspender y reanudar el
thread calculation.

Uso de yield(): Vimos que lo definimos con que hacia de


interprete entre el thread que actualmente esta ejecutando y el
siguiente. Su verdadera misin es resolver situaciones donde
otro thread con la misma prioridad que el thread que se est
ejecutando pueda ejecutarse.

public class YieldApplet extends Applet implements


Runnable{
Thread t;
public void init(){
t = new Thread(this);
}
public void paint(Graphics g){
t.yield();
}
}
Revisemos el uso de mtodo yield sobre el applet fractal:
public class Fractal extends Applet implements
Runnable{
Thread t;
!

public void start(){


if (t==null){
t=new Thread(this);
t.start();
}
}
public void stop(){
t.suspend();
}
public void run(){
while (sectionsToCalculate){
doCalc();
repaint();
t.yield();
}
}
public void paint(Graphics g){
//Draw the section
}
}

En este caso, el applet resuelve el problema ya que cuando se


llama al mtodo repaint(), la mquina virtual cambia el applet
como thread actual pintando el resultado en paint() y cuando
vuelve el applet se bloquea de nuevo empezando los clculos de
nuevo.

Al finalizar el proceso calculation se bloquea. Si volvemos a


llamar el applet el thread calculation se reanima de nuevo con
resumen.

Hemos visto 3 mecanismos de gestin de threads:


! Ajustando el orden de prioridades.
! Mediante los mtodos suspend y resume.
! Mediante el mtodo yield.

Los mtodos mediante prioridades permiten mayor flexibilidad.


Cuando los threads necesitan ser parados o arrancados cada
vez con los mtodos del applet start y stop, merece la pena
usar suspend y resume.
El mtodo yield es el menos usado de todos ya que debido a la
variabilidad de las plataformas sobre las que se asienta Java, es
el mtodo que menos garantas ofrece.

Thread daemon
!

Hay dos tipos de threads en los sistemas Java: threads de


usuario de los que hasta ahora hemos tratado y threads daemon.

Estos ltimos son creados internamente por el API de Java. La


mquina virtual puede crear en nuestro nombre threads daemon.
Tienen tambin ordenes de prioridad como los otros y mtodos
start, stop, suspend y resume.

La mquina virtual chequea y analiza si el thread es uso es de


usuario o daemon despus de que un thread de usuario ha
salido.

A veces, cuando la mquina virtual ve que los threads


remanentes son daemon, sale y finaliza el programa. As, los
threads daemon slo existen para servir a los threads de usuario
y por tanto no hay razones para que el programa prosiga.

Los threads cannicos daemon de la mquina virtual es la


coleccin de threads garbage. Esta es una utilidad de threads
que contiene Java para interactuar con el sistema a travs de la
mquina virtual.

Mtodo setDaemon(boolean on): El conjunto de threads


son daemon si on = true o de usuario si on = false. Es llamado
solo despus de que el thread objeto ha sido creado y antes de
que es arrancado.

Mtodo boolean isDaemon(): Retorna true si el thread es


daemon y false si es de usuario.

Mecanismo de gestin round-robin


!

El uso de prioridades nos resuelve muchos problemas, pero hay


veces que cuando tu tienes threads independientes que
necesitan un mecanismo de gestin del tiempo a pesar de las
plataformas en las que esta ejecutndose.

A veces, pueden haber cientos de threads ejecutndose y


pueden necesitar acceder slo de vez en cuando a la CPU
estando la mayor parte del tiempo bloqueados.

Si solo tenemos un procesador el mecanismo de gestin solo


tiene que dar ordenes de prioridad ms bajo a los threads de la
CPU respecto de los threads de nuestro programa Java.

Mecanismo de gestin round-robin: A cada thread bajo


control se le da una cantidad de tiempo fija de ejecucin tal que
cuando este tiempo transcurre, otro thread entra en ejecucin,
bloquendose el anterior.

Veamos dos razones para su uso:


! Hay limitacin de tiempo tal que un gestor es necesario.
! El desarrollo de un gestor que ilustre el comportamiento que
tu necesitas en programacin con muchos threads arbitrarios.

Ejemplo:
public class SimpleScheduler extends Thread{
int timeslice;
public SimpleScheduler(int t){
timeslice =t;
setPriority(Thread.MAX_PRIORITY);
setDaemon(true);
}
public void run(){
while(true){
try{
sleep(timeslice);
}catch (Exception e){}
}
}
}
Usemos la definicin de esta clase como gestor.
class TestThread extends Thread{
String id;
public TestThread(String s){
id = s;
}
public void run(){
int i;
for(i=0;i<10;i++){
doCalc();
System.out.println(id);
}
}
}

public class Test{


public static void main(String args[]){
new SimpleScheduler(100).start();
TestThread t1,t2,t3;
t1 = new TestThread("Thread 1");
t1.start();
t2 = new TestThread("Thread 2");
t2.start();
t3 = new TestThread("Thread 3");
t3.start();
}
}
En este programa, hay tres threads con prioridad
NORM_PRIORITY y el SimpleScheduler con prioridad
MAX_PRIORITY. El sistema comienza as:
PRIORITY 5: t2->t3->t1->NULL
BLOCKET: SimpleScheduler->NULL
!

Luego es t1 el que se esta ejecutando y dice "thread 1".


Supongamos, que finaliza la espera de SimpleScheduler y se
despierta pasando a estado runnable al tener una prioridad ms
alta, entonces:
PRIORITY 5: t2->t3->t1->NULL
PRIORITY 10: SimpleScheduler->NULL
!

Se ejecuta la espera y vuelve a bloquearse ejecutndose t2 a


continuacin:
PRIORITY 5: t3->t1->t2->NULL
BLOCKET: SimpleScheduler->NULL

Este gestor SimpleScheduler podemos hacerlo ms elaborado


incluyendo tres niveles de prioridad. Ejemplo:
public class CPUScheduler extends Thread{
private int timeslice;
// clase lista circular
// contiene los mtodos
//insert= inserta un elemento a la lista
//delete= elimina un elemento de la lista
//getNext= pasa al siguiente elemento de la
lista.
private CircularList threads;
!

public CPUScheduler(int t){


threads = new CircularList;
timeslice t;
}
public addThread(Thread t){
threads.insert(t);
t.setPriority(2);
}
public removeThread(Thread t){
t.setPriority(5);
threads.delete(t);
}
public void run(){
Thread current;
current.setPriority(6);
while(true){
current = (Thread) thread.getNext();
if(current ==null) return;
current.setPriority(4);
try{
Thread.sleep(timeslice);
}catch (InterruptedException ie){}
current.setPriority(2);
}
}
}

Usemos la definicin de esta clase como gestor.


class TestThread extends Thread{
String id;
public TestThread(String s){
id = s;
}
public void run(){
int i;
for(i=0;i<10;i++){
doCalc();
System.out.println(id);
}
}
}
public class Test{
public static void main(String args[]){
CPUScheduler c=new CPUScheduler(100);
TestThread t1,t2,t3;
t1 = new TestThread("Thread 1");
c.addThread(t1);
t2 = new TestThread("Thread 2");
c.addThread(t2);
t3 = new TestThread("Thread 3");
c.addThread(t3);
t1.start();
t2.start();
t3.start();
c.start();
}
}
Despus de llamara c.start() el estado de prioridades ser:
PRIORITY 2: t2->t3->t1->NULL
PRIORITY 6: CPUScheduler->NULL

El thread de mayor prioridad es CPUScreduler que es el que se


ejecuta con el mtodo run(). Pasa al siguiente thread t1 y asigna
prioridad 4.
PRIORITY 2: t2->t3->NULL
PRIORITY 4: t1->NULL
PRIORITY 6: CPUScheduler->NULL

CPUScheduler esta en estado runnable pero bloqueado lo que


permite a t1 ejecutarse.
PRIORITY 2: t2->t3->NULL
PRIORITY 4: t1->NULL
BLOQUET: CPUScheduler->NULL
!

Cuando CPUScheduler despierta pasa t1 a prioridad 2 y la


prioridad de t2 a 4
PRIORITY 2: t3->t1->NULL
PRIORITY 4: t2->NULL
PRIORITY 6: CPUScheduler->NULL

Y el ciclo continua sucesivamente.

1. Sin embargo la clase CPUScheduler todava tiene problemas


de sincronizacin. Para resolverlo, modificaremos dos de
sus mtodos.
2. Tambin autosalvaremos la prioridad del thread.
3. Qu sucede si dos threads intentan crear un
CPUScheduler?. Para resolver esta situacin
necesitamos inicializar la clase. Podemos usar
una variable esttica en la clase para
diferenciar las clases.
! Se introduce mtodo de sincronizacin para el
acceso a la variable esttica y una excepcin en
el constructor.
4. Podemos cambiar el gestor de forma que se quede
en modo de espera cuando no queden ms threads
que gestionar.
! En el constructor hemos establecido que el
thread sea daemon.
! Tambin hemos establecido que si un thread no
esta en la lista nosotros esperamos con wait()
hasta que uno est disponible. Con notify()
notificamos que el thread estaba en espera y la
condicin se ha cumplido.

public class CPUScheduler extends Thread{


// clase lista circular
// contiene los mtodos
//insert= inserta un elemento a la lista
//delete= elimina un elemento de la lista
//getNext= pasa al siguiente elemento de la
lista.
private CircularList threads;
private Thread current; //1
private int timeslice;
private static boolean initialized = false;//3
private boolean needThreads; //4
private synchronized static boolean
isInitialized(){ //3
if (initialized) return true;
initialized = true;
return false;
}

public CPUScheduler(int t){


if (isInitialized()) //3
throw new SecurityException("Already
initialized");
threads = new CircularList();
timeslice t;
setDaemon(true); //4
}
public synchronized void addThread(Thread t){
t.setPriority(2);
threads.insert(t);
if (needThreads){ //4
needThreads = false;
notify();
}
}

public void removeThread(Thread t){


try{ //2
t.setPriority(5);
}catch(Exception e){}
threads.delete(t);
syncronized(this){ //1
if(currect ==t) current =null;
}
}
public synchronized void run(){
current.setPriority(6);
while(true){
current = (Thread) thread.getNext();
while(current == null){
needThreads=true;
try{
wait();
}catch (Exception e){}
current=(Thread)
threads.getNext();
}
try{ //2
current.setPriority(4);
}catch(Exception e){
removeThread(current);
continue;
}
try{
sleep(timeslice);
}catch (InterruptedException ie){}
if(currect !=null) {
try{ //2
current.setPriority(2);
}catch(Exception e){
removeThread(current);
}
}
}
}
}

En este punto, tenemos un gesto robusto, donde hemos


sincronizado todas las variables de estado internas
correctamente.

Tambin nos hemos asegurado de que no nos aparezcan


excepciones ante los cambios externos que hagamos.

PROBLEMAS:
! Sin embargo, puede suceder que el thread que en ese momento
esta ejecutndose de repente entra en estado de bloqueo.
Supongamos el siguiente estado:
PRIORITY 2: t3->t1->NULL
PRIORITY 4: t2->NULL
BLOQUET: CPUScheduler->NULL
Si t2 se bloquea de repente entonces:
PRIORITY 2: t3->t1->NULL
PRIORITY 4: NULL
BLOQUET: t2->CPUScheduler->NULL
!

Esto significa que t3 comienza a ejecutarse en prioridad 2.


Cuando la CPUScheduler despierta establece t2 con prioridad 2
y pasa t3 a prioridad 4 cuando CPUScheduler tiene el retardo
con sleep.
PRIORITY 2: t1->NULL
PRIORITY 4: t3->NULL
BLOQUET: t2->CPUScheduler->NULL
!

Todo esta bien, pero cuando avanza el proceso, llegara un


momento que t2 pase a prioridad 4. En nuestro gestor no hemos
establecido un camino para decirle que t2 est en estado
bloqueado.

Una forma de resolver esto es generar una clase de forma que


cuando el thread de prioridad 4 se ha bloqueado necesitamos un
thread de prioridad 3. As, cuando un thread de prioridad 3 se
est ejecutando significa que el thread de prioridad 4 est
bloqueado y notifica a la prioridad 6 que un nuevo thread de
prioridad 4 ha de ser seleccionado.

Ha este thread de prioridad 3 le llamaremos "ThreadNotifier"

Este nuevo problema, plantea nuevos dilemas, ya que puede


suceder que se de repente se bloquee uno o todos.

De alguna forma debemos decirle a la CPUSchreduler que si


todos los threads estan bloqueados pase a un estado de espera.
Tambin que si algun thread se desbloquea pueda despertarse y
seguir gestionando el problema. Para ello generaremos la clase
CPUSchedulerNode.

class CPUSchedulerNode{
Thread thread;
boolean blocked;
CPUSchedulerNode(Thread t){
thread = t;
blocket = false;
}
public boolean equals(Object o){
if (thread ==o) return true;
return false;
}
}
class ThreadNotifier extends Thread{
CPUScheduler c;
public ThreadNotifier(CPUScheduler c){
setPriority(3);
this.c = c;
}
public void run(){
boolean done=false;
while (!done){
c.wakeup();
}
}
}

public class CPUScheduler extends Thread{


private CircularList threads;
private CPUSchedulerNode current;
private int nThreads = 0;
private ThreadNotifier notification;
private Thread scheduler;
private int timeslice;
private static boolean initialized = false;
private boolean needThreads = false;
private synchronized static boolean
isInitialized(){
if (initialized) return true;
initialized = true;
return false;
}

public CPUScheduler(int t){


if (isInitialized()) //3
throw new SecurityException("Already
initialized");
threads = new CircularList();
timeslice t;
nThreads = 0;
}
public synchronized void addThread(Thread t){
CPUSchedulerNode n = new
CPUSchedulerNode(t);
t.setPriority(2);
threads.insert(n);
nThreads++;
if (needThreads){ //4
needThreads = false;
notify();
}
}

public synchronized void removeThread(Thread


t){
// locate mtodo de listaCircular
// delete mtodo de listaCircular
Object n=thread.locate(t);
threads.delete(n);
if(currect ==t) current =null;
nThreads--;
}
public synchronized void wakeup(){
notify();
}
public void startScheduler(){
notification = new ThreadNotifier(this);
notification.setDaemon(true);
notification.start();
scheduler = new Thread(this);
scheduler.setDaemon(true);
scheduler.start();
}
public void stopScheduler(){
scheduler.stop();
notification.stop();
}
public synchronized void run(){
long now,then;
int nBloqued = 0;
Thread.currentThread().setPriority(6);
now = System.currentTimeMillis();
while(true){
current = (CPUSchedulerNode)
thread.getNext();
while(current == null){
needThreads=true;
try{
wait();
}catch (Exception e){}
current=(CPUSchedulerNode)

threads.getNext();
}
try{
current.Thread.setPriority(4);
}catch(Exception e){
removeThread(current.Thread);
continue;
}
then = now;
if(nBloqued==nThreads)
notification.suspend();
else notification.resume();
try{
wait(timeslice);
}catch (InterruptedException ie){}
now =System.currentTimeMillis();
if(currect !=null) {
try{
current.Thread.setPriority(2);
if (now-then <timeslice){
//thread bloqueado
if(!current.blocked){
current.blocked=true;
nBlocked++; // contaje de
bloqueos.
}
}
else{
if(current.blocked){
current.blocked=false;
nBlocked--; // contaje de
bloqueos.
}
}
}catch(Exception e){
removeThread(current.thread);
}
}
}
}
}

APENDICE: Cdigo de la lista Circular


public class CircularListNode{
Object o;
CircularListNode next;
CircularListNode prev;
}
public class CircularList{
private CircularListNode current;
public synchronized void insert(Object o){
CircularListNode tn = new
CircularListNode();
tn.o=o;
if (current = null){
tn.next = tn.prev = tn;
current = tn;
}else{
tn.next = current;
tn.prev = current.prev;
current.prev.next = tn;
current.prev = tn;
}
}
public synchronized void delete(Object o){
CircularListNode p = find(o);
CircularListNode next = p.next;
CircularListNode prev = p.prev;
if (p == p.next){ // ultimo elemento
current = null;
return;
}
prev.next = next;
next.prev = prev;
if (current == p) current =next;
}

private CircularListNode find(Object o){


CircularListNode p = current;
if (p == null)
throw new IllegalArgumentException();
do{
if(p.o == o) return p;
p = p.next;
}while p != current);
throw new IllegalArgumentException();
}
public synchronized Object locate(Object o){
CircularListNode p = current;
do{
if (p.o.equal(o)) return p.o;
p = p.next;
}while (p != current);
throw new Illegal ArgumentException();
}
public synchronized Object getNext(){
if (current == null) return null;
current = current.next;
return current.o;
}
}

Vous aimerez peut-être aussi