Download Java

Document related concepts
no text concepts found
Transcript
75-62 Técnicas de
Programación
Concurrente II
2004
java Threads
FIUBA
Ing. Osvaldo Clúa
Bibliografía: tutorial de Java en http://java.sun.com
Un buen libro (algo teórico) es Garg: Concurrent and Distributed Computing in
Java Puede haber material que ayude en las páginas de sus clases en http:
Se presupone un conocimiento general de Java. En Internet hay bastantes cursos
para poder seguir. En la página Web de la materia se dejó una colección de guías de
estudio que se usó en años anteriores.
Threads
Para crear un thread en Java existen dos opciones:
• Extender la clase Thread.
• Implementar la interface Runnable.
La elección de alguna de estas opciones depende del diseño general. Al no
admitir Java herencia múltiple, la implementación de interfaces sirve para denotar
propiedades secundarias al eje de herencia. El siguiente es un ejemplo de estas dos
formas de hacerlo:
1. /**
2. * Primero.java
3. *
4. * El HolaMundo de las threads
5.
*/
6.
7.
class T1 extends Thread
8.
{
9.
public void run() {
10.
System.out.println
11.
("Hola, soy tu primer Thread, extendi
la clase Thread "+
12.
Thread.currentThread());
13.
}
14.
}
15.
class T2 implements Runnable
16.
{
17.
public void run() {
18.
System.out.println
19.
("Hola, soy tu primer Thread,
implemente Runnable " +
20.
Thread.currentThread());
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
21.
}
22.
}
23.
24.
public class Primero
25.
{
26.
public static void main(String args[]) {
27.
T1 t1 = new T1();
28.
T2 ru = new T2();
29.
t1.start();
30.
Thread t2 = new Thread (ru);
31.
t2.start();
32.
System.out.println("Hola, soy el Thread
principal"+
33.
Thread.currentThread());
34.
try{
35.
t1.join();
36.
t2.join();
37.
}
38.
catch(InterruptedException e){
39.
e.printStackTrace();
40.
System.exit(0);
41.
}
42.
System.out.println("Chau, solo queda el
Thread principal"+
43.
Thread.currentThread());
44.
}
45.
}
En el código anterior se ven las dos formas de crear un thread. Al llamar al
método start() de Thread se crea efectivamente el Thread y se le da control al método
run() que es el que hay que programar. En el caso de implementar runnable se debe
crear el thread para que haya acceso a este método.
En la clase Thread están definios los métodos start() y join() con el significado
habitual.
La salida de este programa se ve así:
1.Hola, soy tu primer Thread, extendi la clase
Thread Thread[Thread-0,5,main]
2.Hola, soy el Thread principalThread[main,5,main]
3.Hola, soy tu primer Thread, implemente Runnable
Thread[Thread-1,5,main]
4.Chau, solo queda el Thread principalThread
[main,5,main]
-2-
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
Donde:
Thread-0, Thread-1 y principalThread son los nombres de los threads. 5 es la
prioridad y main es el grupo al que pertenecen.
Estados de un thread:
Un thread Java puede estar en uno de cuatro estados posibles:
• New. Cuando el objeto se crea (usando la isntrucción new()).
• Runnable. Una vez llamado el método start().
• Blocked. Cuando se está a la espera de una operación de I/O o se llamó a los
métodos sleep(), suspend() (este último está desaprobado y se quitará de
alguna versión posterior de Java)
• Dead.. Cuando se terminó el run(...) o se llamó a stop() (también
desaprobado).
Como ejemplo mas completo se propone un conjunto de dos contadores sobre un
mismo canvas:
1. /* El contador */
2. import java.awt.*;
3.
4. public class Cont implements Runnable
5. {
6.
long cont=0;int aDormir;
7.
Color color;
8.
9. Cont(int tiempo, Color c){
10.
color=c; aDormir=tiempo;
11.
}
12.public void run() {
13.
while (true){
14.
try {Thread.sleep(aDormir);}
15.
catch (InterruptedException e) {}
16.
cont++;
17.
}
-3-
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
18.
}
19.public void paint(Graphics g) {
20.
g.setColor(color);
21.
g.setFont(new Font
(null,Font.PLAIN,24));
22.
g.drawString( String.valueOf(cont), 10,
30);
23.
}
24.}
Este contador duerme según un parámetro de construcción (tiempo) y se
incrementa en uno. Además tiene la responsabilidad de dibujarse en paint.
Una aplicación usa de este contador, creando uno distinto en un canvas que
divide en dos graphics:
1. /* La aplicacion del contador */
2. import java.awt.*;
3. import java.awt.event.*;
4.
5. class ContAp extends Frame implements Runnable{
6.
Thread tic,tac,anima;
7.
Cont c1,c2;
8.
Canvas c;
9.
int ancho=200,alto=300;
10.ContAp(String s){
11.
super(s);
12.
c1=new Cont(500,Color.black);
13.
c2=new Cont(1000,Color.blue);
14.
tic=new Thread(c1);tac=new Thread(c2);
15.
c=new Canvas ();
16.
c.setSize(ancho,alto);
17.
add(c);
pack();
setVisible(true);
18.
tic.start();tac.start();
19.
addWindowListener(new WindowAdapter()
20.
{public void windowClosing(WindowEvent e)
{System.exit(0);}});
21.
anima=new Thread(this);anima.start();
22.
repaint();
23.
}
24.public void paint (Graphics g){
25.
Graphics cg=c.getGraphics();
26.
cg.setColor(Color.LIGHT_GRAY);
27.
cg.fillRect(0,0,ancho,alto);
28.
Graphics g1=cg.create(0,0,ancho,alto/2);c1.paint
(g1);
-4-
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
29.
Graphics g2=cg.create(0,alto/2,ancho,alto/2);
c2.paint(g2);
30.
}
31.public void run() {
32.
while (true) {
33.
try {Thread.sleep(250);}
34.
catch (InterruptedException e) {}
35.
repaint();
36.
}
37.
}
38.
39.}
En el método paint se ve como divide el graphic en dos y le pide a cada
contador que lo dibuje. Falta una clase principal que tenga el método public static void
main(String s[]) para completar el ejemplo.
Se usaron 3 threads: tick, tack y anima. Las dos primeras son de cada contador
en tanto que la última es el thread necesario para la animación. Pruebe sin ella a ver
que pasa.
En el caso anterior cada elemento tiene su contexto gráfico propio. El ejemplo de
los puntos adjunto a esta práctica muestra una variación compartiendo un único
canvas.
Como último ejemplo simple, se analizará la aplicación de puntos que rebotan en
un canvas.
Acá los puntos son una clase cuya responsabilidad es dibujarse, mantener su
posición y velocidad. La clase PunCan contiene al canvas, la inicialización y el
método paint() para dibujarse. En este caso se usó una técnica de animación conocida
como Double Buffer. en la clase PunCan se hizo override de update:
public void update(Graphics g){paint(g);}
Evitando que se borre la ventana con cada llamado. Se definió:
Graphics miGr;
Image miIm;
-5-
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
Para tener donde trabajar offscreen. La image se crea del mismo tamaño que el
canvas:
miIm=createImage((new Double(ca.getBounds().getWidth
())).intValue(),(new Double(ca.getBounds().getHeight
())).intValue());
miGr=miIm.getGraphics();
Y de esta imagen se crea un nuevo contexto gráfico. Se dibuja sobre este
contexto gráfico y se envía toda la imagen al canvas de la pantalla en un solo paso:
for(int i=0;i<cant;i++)
{p[i].paint(miGr);}
Graphics g1=ca.getGraphics();
g1.drawImage(miIm,0,0,this);
}
Esto anula el flicker.
Es importante que entienda como cambiar la velocidad de animación.
Sincronización
El siguiente es el código de un productor consumidor "automático" para su uso
desde la consola de texto:
1. /**
2. * Buffer.java
Con primitivas de sincronizacion
3. */
4.
5. public class Buffer
6. {
7.
private static final int
BUFFER_SIZE = 5;
8.
private int cant; // items en el buffer
9.
private int in;
// Proximo libre
10.
private int out; // Proximo lleno
11.
private Object[] buffer;
13.
14.
15.
16.
17.
18.
19.
public Buffer()
{
cant = 0;
// Comienza Vacio
in = 0;
out = 0;
buffer = new Object[BUFFER_SIZE];
}
21.
22.
23.
public synchronized void poner(Object item) {
while (cant == BUFFER_SIZE) {
try {wait();}
-6-
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
24.
catch (InterruptedException e) { }
25.
}
26.
++cant;
//Agrega al buffer
27.
buffer[in] = item;
28.
in = (in + 1) % BUFFER_SIZE;
29.
if (cant == BUFFER_SIZE)
30.
System.out.println("Entro " + item +
31.
" Buffer LLENO");
32. else
33.
System.out.println("Entro " + item +
34.
" Buffer Size = " + cant);
35.
notify();
// despierta algun consumidor
36.
}
37.
public synchronized Object sacar() {
38.
Object item;
39.
while (cant == 0) {
40.
try {wait(); }
41.
catch (InterruptedException e) { }
42.
}
43.
--cant;
44.
item = buffer[out];
45.
out = (out + 1) % BUFFER_SIZE;
46.
if (cant == 0)
47.
System.out.println("Salio " + item + " Buffer
VACIO");
48. else
49.
System.out.println("Salio " + item +
50.
" Buffer Size = " + cant);
51.
notify();
52.
return item;
53.
}
54. }
Este buffer es el objeto encargado de hacer la sincronización entre el productor y
el consumidor. No es un objeto activo (No tiene threads). En Java cada objeto tiene un
lock (monitor es el meta-lenguaje de Java). Al ejecutar un método synchronized se
toma este monitor y se lo libera al terminar la ejecución del método. Solo un thread
puede tener el monitor de un objeto. El lazo de sincronización está en el uso de:
wait() que libera el monitor del objeto y suspende al thread que lo llama hasta
que ocurra una interrupción.
notify() que provoca una interrupción a algún thread que espera por el monitor
del objeto.
notifyAll() que provoca una interrupción en todos los threads que esperan por el
monitor del objeto.
Como pueden interrumpirse (y salir del wait( )) por otras causas, se hace el while
-7-
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
por la condición de las líneas 23 y 40.
El productor es:
1. /**
2. * Productor.java
3. */
5. import java.util.*;
7. public class Productor extends Thread
8. {
9.
private Buffer buffer;
10.
final static int NAP_TIME=5;
11.
public Productor(Buffer b) {buffer = b;}
12.
public void run()
13.
{
14.
Date mens;
15.
while (true){
16.
dormir();mens = new Date();
17.
System.out.println("Producido " + mens);
18.
buffer.poner(mens);
19.
}
20.
}
21.
public static void dormir() {
// para
suspenderse
22.
int sleepTime = (int) (NAP_TIME * Math.random
() );
23.
try { Thread.sleep(sleepTime*1000); }
24.
catch(InterruptedException e) { }
25.
}
26. }
y el consumidor
1. /**
2. * Consumidor.java
3. */
5. import java.util.*;
7. public class Consumidor extends Thread
8. {
9.
private Buffer buffer;
10.
final static int NAP_TIME=5;
11.
public Consumidor(Buffer b)
12.
{buffer = b;}
13.
-8-
FIUBA 2004
756-62 Técnicas de Programación Concurrente II
14.
public void run()
15.
{Date mens;
16.
while (true){
17.
dormir();
18.
System.out.println("Consumidor quiere
consumir.");
19.
mens = (Date)buffer.sacar();
20.
}
21.
}
22. public static void dormir() {
// para
suspenderse
23.
int sleepTime = (int) (NAP_TIME *
Math.random() );
24.
try { Thread.sleep(sleepTime*1000); }
25.
catch(InterruptedException e) { }
26.
}
27. }
Para su ejecución hace falta una clase como la que sigue:
1. /** * Prubuf.java */
4. public class Prubuf
5. {
6. public static void main(String args[]) {
7.
Buffer sr = new Buffer();
8.
Productor pt = new Productor(sr);
9.
Consumidor ct = new Consumidor(sr);
10.
pt.start(); ct.start();
11. }
12. }
En el archivo zip correspondiente a esta guía encontrará un productor-consumidor
con interface gráfica como la que sigue. El código sin embargo no hace uso de un
thread continuo como el primer código.
Ejercicios:
1. Resolver el Productor Consumidor con un thread para cada uno mas los threads
necesarios para la interface gráfica.
2. Generalizarlo para "n" productores y "k" consumidores (a ingresar desde la
interface).
3. Resolver con una interface gráfica parecida el problema de los Fumadores.
4. Haga un programa que permita hacer Drag & Drop de una forma simple a través de
un canvas. Use las clases asociadas a Java2D para determinar los límites y dibujar
las formas (no hay threads).
5. Programe un juego simple con una víbora que se mueve en la pantalla. Ud dispone
de una palmeta (rectángulo) para aplastarle la cabeza. Si no lo logra, una marca de
la palmeta queda en la pantalla y la víbora debe esquivarla.
-9-