Programación Avanzada.
Centro Superior de Informática.
Universidad de La Laguna.
Implementación de Flujos de Control Múltiples
(Threads).
En ejercicios anteriores se han desarrollado aplicaciones y applets que
dibujan un reloj y muestran la fecha y la hora actuales. El reloj no camina
después de mostrar la hora. La cuestión que nos plantearemos
en está práctica es: ¿Cómo podemos conseguir
que el reloj muestre la hora actualizada cada segundo?. La clave
de la respuesta está en volver a pintar (repaint) el reloj
con la hora actualizada. Por lo tanto, se podría intentar sobreescribir
el método start() del
applet de la siguiente manera:
public void start() {
while (true) {
repaint();
try {
Thread.sleep(1000);
}
catch( InterruptedException
ex) {
}
}
}
El método start() se
llama cuando el applet empieza a ejecutarse. El bucle infinito dibuja un
reloj cada 1.000 milisegundos. Por lo tanto, parece que se debería
refrescar la pantalla cada segundo con un nuevo reloj, sin embargo, si
se ejecuta el programa sobre un un navegador, se nos cuelga. El problema
reside en que mientras se ejecute el bucle infinito, el navegador no puedrá
atender a ningún otro evento que pueda estar sucediendo. Por lo
tanto, el metódo paint() no
es llamado nunca. Así pues, la solución a nuestro problema
consiste en mover el bucle while a otro thread que se ha de ejecutar en
paralelo con el thread del applet que ejecuta el método paint().
Para crear un Thread para un applet, es necesario implementar la interface
Runnable. Para ello podemos seguir los siguientes pasos generales:
-
Añadir en la declaración de la clase implements Runnable:
public class TestApplet extends
Applet implements Runnable
-
Declarar un objeto Thread en
el applet TestApplet. Por ejemplo, las siguientes sentencias declaran
una instancia de la clase Thread,
timer, con valor inicial nulo:
private Thread timer = null;
Por defecto, el valor inicial es nulo, así que la asignación
al valor "null" no es necesaria.
-
Crear un thread (con el operador new) en el método init()
del
applet y ponerla en marcha (llamando a su método
start()):
public void init(){
timer = new Thread(this);
//crear el thread
timer.start(); //poner
en marcha el thread
}
El argumento "this" en el constructor del thread es indispensable,
puesto que especifica que el método run()
de TestApplet es el que se debe llamar cuando se ejecute el thread.
Nótese que el método start()en
timer.start() es diferente del método start()
del
applet. El método start()
del
thread provoca que el método run()
se
ejecute, mientras que el método start() del applet es invocado por
el navegador cuando se abre por primera vez la página que contine
al applet o cuando se activa por sucesivas visitas.
-
Reanudar el thread en el método start()
del applet mediante una llamada a un método resume():
public void start(){
resume();
}
El método resume() reanuda al thread (lo pone en marcha
otra vez) si había sido suspendido. Este método es
ignorado if el thread no había sido suspendido. Puesto que el método
resume() de la clase Thread
ha sido desautorizado (deprected), es necesario que el usuario implemente
el suyo propio en su programa.
-
Implementar los métodos resume() y suspend()como
sigue:
public synchroized void resume(){
if (suspended){
suspended = false;
notify();
}
}
public synchronized void suspend(){
suspended = true;
}
La variable suspended se debe declarar como una variable
miembro de la clase, y en ella se indica el estado del thread. La palabra
reservada "synchronized" aseguran que los métodos resume()
y
suspend() se ejecutan en serie (uno después que el otro)
para así evitar las condiciones de carrera (race conditions) que
podrían provocar un resultado inconsistente de los valores de la
variable suspended.
-
Escribir el código que se quiere que ejecute el thread en el método
run():
public void run(){
while (true){
repaint();
try{
timer.sleep(1000);
synchronized(this) {
while(suspended) wait();
}
}
catch (InterruptedException
ex) {
}
}
}
El método run() es invocado
por el sistema de ejecución de Java cuando el applet empieza (start).
El bucle while repite la llamada al método repaint()
cada segundo si el thread no esta suspendido. Si la variable
suspended está a true, el método wait()
provoca que el thread se suspenda y espere por la notificación
que le llegará mediante la llamada al método notify()
en el método resume(). El método
repaint() se ejecuta sobre el
thread por defecto del sistema, el cual está separado del thread
sobre el que se ejecuta el bucle while. La palabra reservada "synchronized"
elimina los conflictos potenciales que podría causar el que el thread
suspendido perdiera una notificación y permaneciera suspendido.
-
Redefinir el método stop() de
forma que suspenda el thread que se está ejecutando:
public void stop() {
suspend();
}
Este código suspende el thread de manera que no consume tiempo de
CPU mientras la página Web que contiene al applet esté inactiva.
-
Redefinir el método destroy() para
que mate al thread:
public void destroy () {
timer = null;
}
Este código libera todos los recursos asociados con el thread cuando
se sale del navegador.
Aunque este ejercicio muestre como implementar la interface Runnable en
un applet, dicha interface se puede implementar en cualquier clase.
Bibliografía.
[1] Y. Daniel Liang. "Introduction to Java Programming".
QueE&T. 1999
[2] S. Davis. "Aprenda Java ya". McGraw-Hill. Microsoft Press. 1996.
[3] J. Gosling, H. McGilton. "The Java Language Enviroment".
A White Paper. Sun Microsystems. Java Soft. 1996.
[4] D. Flanagan. "Java en pocs Palabras". McGrawHill-O'Reilyy. 1998.
[5] J.F. Macary, C. Nicolas. "Programación Java". Eyrolles.