formato *

Anuncio
Práctica 12
San Sebastián, mayo 1999
Programación Java
Javier García de Jalón · José Ignacio Rodríguez
Alfonso Brazález · Alberto Larzabal · Jesús Calleja · Jon García
Informática 2: Práctica nº 12
página 1
INDICE
Ejercicio 1:
Ejercicio 2:
Ejercicio 3:
Ejercicio 4:
Ejercicio 5:
Creación de la clase Timer...................................................................................................... 1
Sincronizar el acceso a un objeto I .......................................................................................... 3
Sincronizar el acceso a un objeto II ......................................................................................... 3
Movimiento oscilatorio de un objeto en la pantalla.................................................................. 4
Introducción de la técnica del doble buffer en la clase OscilaCanvas ....................................... 5
Antes de comenzar la práctica abre el Windows Explorer y comprueba que se ha creado de modo
automático en tu disco G:\ un directorio llamado Inf2prac12. No deberás moverlo a otro
subdirectorio ni cambiarle de nombre. Por motivos de orden es importante que todos los ejercicios
de esta práctica se creen dentro de este directorio, porque esta semana se recogerá la práctica.
Como recomendación general, mantén abierto el Windows Explorer y comprueba de vez en
cuando que los proyectos de los distintos ejercicios se están guardando correctamente. Hay que
evitar copiar los ejercicios de otra persona, principalmente porque así no se aprende a programar: a
programar sólo se aprende programando.
Puedes utilizar también Windows Explorer para ayudar a Visual J++ 6.0 a crear un proyecto
nuevo a partir de los ficheros del anterior.
Ejercicio 1:
Creación de la clase Timer
En este ejercicio se trata de crear una clase denominada Timer que actúe de una forma similar al
objeto Timer que aparece en el entorno de programación Visual Basic. Consistía en incluir un
objeto Timer en algún módulo o formulario del proyecto y programar una función
Timer_NombreTimer() que se ejecutaba cada cierto número de milisegundos (propiedad Interval).
Existía además la posibilidad de pararlo o arrancarlo nuevamente con la propiedad Enabled (valores
true o false). Crea un proyecto llamado Ejer1 dentro de G:\Inf2Prac12 y guarda en él los ficheros
que vayas creando.
La clase Timer (se guardará en un fichero llamado Timer.java) se deberá encargar de ejecutar
cada cierto número de milisegundos el método timer() implementado por otras clases . Se creará
una interface llamada ITimer con un único método void timer() el cual deberá ser implementado
por aquellas clases que deseen tener una función que se ejecute repetidamente. A continuación se
muestra la interface ITimer:
// interface ITimer.java
public interface ITimer{
public void timer();
}
// función a implementar
Las variables miembro de la clase Timer serán:
private
private
private
private
private
Thread
String
long
boolean
ITimer
t;
name;
interval;
enabled;
func;
//
//
//
//
//
//
thread utilizado para ejecutar el método run()
nombre del Timer
intervalo de tiempo entre ejecuciones
propiedad para indicar si está activo
referencia a la clase que contiene el método
timer() a ejecutar
Implementará la interface Runnable de forma que el thread t ejecutará el método run() de
esta clase Timer. El constructor de Timer ( public Timer(String nombre, ITimer func) )
recibirá como argumento una referencia a un objeto que implemente la interface ITimer. Será el
método run() de la clase Timer el encargado de llamar a la función timer() de esa referencia (func)
Informática 2: Práctica nº 12
página 2
con el intervalo de tiempo indicado por la variable interval. Además deberá definir los siguientes
métodos públicos:
String getName()
void setInterval(long i)
long getInterval()
void setEnabled(boolean val)
void run()
/**
/**
/**
/**
/**
devuelve el nombre del Timer */
se asigna el intervalo en milisegundos*/
se lee el intervalo en milisegundos*/
se para o arranca el thread */
método principal de Timer */
Se creará un fichero PruebaTimer.java desde donde se probará la clase Timer junto con la
interface ITimer. Esta clase contiene el programa principal main(). Como el entorno Visual J++
cierra la consola y la aplicación en cuanto termina la ejecución, sin permitir ver con calma los
resultados, se ha incluido una “espera” por medio de un mensaje y la lectura de un carácter, que
debe producirse antes de terminar la aplicación. El fichero es el siguiente:
// fichero PruebaTimer.java
// prueba de la clase Timer
public class PruebaTimer implements ITimer{
private Timer miTimer;
private int n=0;
public PruebaTimer(){
miTimer = new Timer("MiTimer",this);
}
// método para arrancar el Timer
public void arranca(){
miTimer.setEnabled(true);
}
// método que se ejecuta cada intervalo de tiempo
public void timer(){
n++;
System.out.println("Ejecutado "+n+" veces...");
if(n==5){
// cuando n llega a 5 duplicamos la velocidad
miTimer.setInterval(miTimer.getInterval()/2);
}
if(n==20){ // cuando n llega a 20 paramos el Timer
miTimer.setEnabled(false);
System.out.println("paramos el timer...");
}
}
/** inicio del programa */
public static void main(String[] args) {
PruebaTimer mT = new PruebaTimer();
mT.arranca();
// se espera a pulsar Intro para terminar
try {
while ((Thread.currentThread().activeCount() )> 1) {
Thread.currentThread().sleep(200);
}
System.out.print("Pulse return para finalizar ");
System.in.read();
} catch (java.io.IOException e) {}
catch (InterruptedException ie) {}
} // fin del método main
} // fin de la clase PruebaTimer
Informática 2: Práctica nº 12
Ejercicio 2:
página 3
Sincronizar el acceso a un objeto I
El uso de varios threads o hilos puede enriquecer mucho un programa permitiendo realizar varias
tareas simultáneamente. Pero a su vez si en un programa coexisten más de un thread la
programación se complica. Por un lado es necesario estudiar el impacto que puede tener el hecho de
que dos o más threads puedan modificar o acceder simultáneamente a las variables de un mismo
objeto o clase. Por otro, hay que tener en cuenta la sincronización de varios threas ya que se puede
llegar un punto donde un thread tenga que esperar a que otros finalicen una tarea antes de que aquel
pueda continuar (por ejemplo que un segundo thread termine de leer un fichero). En este ejercicio
se va a presentar un ejemplo del primer caso, es decir de accesos y modificaciones simultáneas.
Crea un proyecto llamado Ejer2 con Visual J++ en el directorio de la práctica Inf2prac12.
Copia a este directorio los ficheros VectorTest.java y VectorSinc.java que se encuentran en
Q:\Infor2\Prac12\Ejer2.
El fichero VectorSinc.java incluye un vector cuyos métodos no están sincronizados. El
constructor de la clase ( public VectorSinc(int n) ) crea un vector de tamaño n y le asigna valores
de 1 a n. Tiene dos métodos: el método showVals() muestra por pantalla los valores del vector y el
método reverseVals() invierte el orden de los números.
El funcionamiento deseado del programa es que los valores del vector estén siempre
ordenados de 1 a n o de n a 1. Si el programa tuviera un sólo thread no habría ningún problema ya
que sólo se podría ejecutar un método simultáneamente. Pero, ¿qué pasa si hay dos o más threads
actuando sobre un mismo objeto de la clase VectorSinc? El funcionamiento puede ser incorrecto ya
un thread puede estar modificando los valores utilizando reverseVals() y otro puede estar leyendo
los valores con showVals() sin terminar de modificar. Incluso se puede dar el caso de que dos
threads intenten invertir el orden de los valores simultáneamente con lo que el resultado es
impredecible. En la función reverseVals() se ha incluido un sleep() para poder observar el mal
funcionamiento ya que debido al poco tiempo que tardaría en ejecutarse esta función habría pocas
posibilidades (aunque no nulas) de que ocurriera.
Ejecuta varias veces el programa VectorTest.java y observa su funcionamiento. Verás que
muchas veces los valores no salen ordenados.
Modifica el fichero VectorSinc.java incluyendo a los dos métodos el indicador synchronized.
De esta forma se consigue que no puedan ejecutarse simultáneamente dos métodos sobre un mismo
objeto de la clase VectorSinc. Si mientras un thread ejecuta el método reverseVals() o showVals()
el otro intenta ejecutar uno de los dos métodos este último se quedará bloqueado hasta que el
primero haya finalizado la ejecución del método. Vuelve a ejecutar varias veces el programa
VectorTest. En este caso los valores deberían aparecer siempre correctamente.
Es importante tener en cuenta que si se añade un método a la clase que no esté sincronizado
éste siempre se puede ejecutar aunque en ese mismo momento se esté ejecutando un método
sincronizado. Prueba a quitar la palabra synchronized del método showVals() y ver qué ocurre.
Ejercicio 3:
Sincronizar el acceso a un objeto II
En este ejercicio se presenta el caso de un thread que llega a un punto donde los datos a leer no se
encuentran preparados y tiene que esperar por ellos.
Crea un proyecto llamado Ejer3 con Visual J++ en el directorio de la práctica Inf2prac12.
Copia a este directorio los ficheros Bandeja.java, Cocinero.java , Comensal.java, y
Restaurante.java que se encuentran en Q:\Infor2\Prac12\Ejer3.
A partir del nombre de los ficheros es fácil adivinar sobre qué tema está basado el ejercicio.
Se trata de un restaurante donde como es habitual el cocinero tarda un tiempo en preparar los
Informática 2: Práctica nº 12
página 4
distintos platos. El comensal (es uno de los threads) pide (método get() de Bandeja) un plato a un
objeto Bandeja y si no hay un plato preparado debe esperar (método wait()) a que esté preparado
por el cocinero (otro thread) quién pondrá el plato en la bandeja utilizando el método put() de
Bandeja. La condición que indica si hay un plato preparado es el estado de preparado, variable
miembro de la clase Bandeja. Obsérvese que si preparado es false, el método get() detiene el thread
(llamando a wait()) hasta que se llame al método notify() o notifyAll() de la clase Bandeja. Es el
método put() quién llama a notifyAll() una vez preparado el plato y después de asignar preparado =
true.
En este ejercicio se pide leer detenidamente y comprender el código de las distintas clases.
El método wait() espera indefinidamente a que sea llamado el método notify() o notifyAll().
Cambia el método wait() por wait(1000) y comprueba qué sucede. En este segundo caso este
método esperará como máximo (si no se llama antes a notify() o notifyAll()) 1000 milisegundos
volviéndose a comprobar nuevamente si preparado es true.
Ejercicio 4:
Movimiento oscilatorio de un objeto en la pantalla
En este ejercicio se trata de crear un applet que represente un movimiento oscilatorio. Un applet es
una aplicación que se ejecuta en un browser al cargar una página HTML que contiene las tags
<APPLET…> … </APPLET>. Tal y como se indica en el manual los applets no tienen ventana
propia sino que es el browser quién les asigna un panel donde dibujar. Otra notable diferencia es la
forma de arrancar el programa: mientras que en las aplicaciones "independientes" comienzan por el
método main(), las applets comienzan por el método init() de la clase Applet. El browser crea un
objeto de la clase derivada de Applet, llama al método init() y posteriormente al método start(),
ambos de la clase Applet. No se llama por lo tanto al constructor de la clase. La labor que
normalmente realiza el constructor se debe pasar a la función init(). La clase Applet deriva de Panel
y por lo tanto hereda todas los métodos gráficos.
Crea un proyecto llamado Ejer4 con Visual J++ en el directorio de la práctica G:\Inf2prac12.
Copia a este directorio los ficheros Timer.java e ITimer.java realizados en el primer ejercicio de
esta práctica. Estos ficheros serán utilizados para realizar la animación.
Crea una nueva clase llamada Oscila (en un fichero Oscila.java) que herede de la clase
Applet. Crea también un fichero llamado AppletOscila.html e incluye el código necesario para
ejecutar Oscila.class como applet. El objeto móvil se dibujará en una nueva clase llamada
OscilaCanvas (derivada de Canvas) la cual será añadida al panel del applet. Esta clase
OscilaCanvas tendrá el método public void dibujar(long t) que se encargará de llamar al
método paint() una vez actualizada la variable time que será miembro de la clase OscilaCanvas. La
posición del móvil se evaluará mediante el método getX() que deberá ser miembro de OscilaCanvas
(la variable tiempo estará expresada en milisegundos):
// método que define el movimiento horizontal
public int getX(long t) {
return (int)(100*Math.sin(1.5*t/1000));
}
La clase Oscila tendrá una variable miembro que represente el tiempo en milisegundos de la
animación. Además creará un objeto de tipo Timer para realizar la animación. Deberá implementar
la interface ITimer y por lo tanto definir el método timer(). Este método aumentará el tiempo y
llamará al método dibujar() de la clase OscilaCanvas pasándole dicho tiempo
Se incluirá un botón de forma que sea posible detener y arrancar el móvil.
Informática 2: Práctica nº 12
página 5
Figura 1. Visualización de movimiento oscilatorio utilizado como aplicación
Además de poder funcionar como applet, es habitual en muchos casos incluir el método
main() que posibilite ejecutar la clase derivada de Applet como una aplicación fuera de un
navegador. En este caso el método main() debe realizar las labores que realiza el browser, esto es:
crear una ventana donde incluir el applet, crear un objeto de ese tipo, llamar a su método init() y por
último llamar al método start(). Copia la clase VentanaCerrable.java a la carpeta del proyecto,
incluye en la clase Oscila el siguiente código y pruébalo como aplicación (ver Figura 1):
// se define un programa principal para poder
// ejecutarse como aplicación
public static void main(String []args) {
// se crea un frame (no se puede derivar de VentanaCerrable)
VentanaCerrable vc = new VentanaCerrable("Applet como aplicación");
vc.setSize(300, 200);
vc.setLocation(100, 100);
// se crea un objeto de la clase que deriva de Applet
// se recuerda que los applets descienden de Panel
Oscila osc = new Oscila();
// se añade el applet a la zona centro del frame
vc.add(osc, "Center");
// se llama a los métodos fundamentales del applet
osc.init();
osc.start();
// se muestra la ventana
vc.show();
} // fin método main()
Ejercicio 5:
Introducción de la técnica del doble buffer en la clase OscilaCanvas
Este ejercicio es muy similar al anterior. Crea un proyecto llamado Ejer5 y copia en él todos los
ficheros del ejercicio anterior. Se trata de modificar la clase OscilaCanvas de forma que la
animación se realice utilizando la técnica del doble buffer.
Esta técnica consiste en no pintar directamente sobre el componente que se encuentra en la
pantalla sino que se pinta sobre una imagen sin mostrar. Una vez que se ha finalizado de "construir"
la imagen, está se mostrará directamente sobre el componente. De esta forma se consigue una
notable mejora en el parpadeo de las imágenes al redibujar. A su vez de debe redefinir el método
update() para que no borre la anterior imagen.
Descargar