0% encontró este documento útil (0 votos)
3 vistas18 páginas

Multithreading en Java: Implementación y Sincronización

Hilos java

Cargado por

lunna_ed
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
3 vistas18 páginas

Multithreading en Java: Implementación y Sincronización

Hilos java

Cargado por

lunna_ed
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Multithreading (Hilos)

6
Contenido
6.1 Introducción ............................................................................................... 206
6.2 Implementar threads en Java .................................................................... 207
6.3 Sincronización de threads ......................................................................... 216
6.4 Resumen .................................................................................................... 221

Objetivos del capítulo


• Ejecutar tareas recurrentes mediante el uso de hilos (threads).
• Conocer las problemáticas asociadas a los entornos multithread.
• Aprender a sincronizar hilos.
• Entender el ciclo de vida de un thread.
206 6 Multithreading (Hilos)

6.1 Introducción
Hasta aquí hemos trabajado con programas lineales. En todos los ejemplos planteados
en los capítulos anteriores, podíamos visualizar la secuencia en que las instrucciones
(o líneas de código) se ejecutaban, una tras otra. Sin embargo, para cierto tipo de pro-
gramas mantener un ujo lineal de sus instrucciones no resulta del todo e ciente y ten-
dremos que pensar en la posibilidad de que diferentes porciones de código se ejecuten
concurrentemente (en paralelo).
Un ejemplo de esto es cómo funciona un programa servidor (un server). Un server es
un programa que constantemente está esperando recibir la conexión de un cliente (un
programa cliente). Cuando este se conecta se establece una comunicación entre ambos
y luego de un “diálogo” en el cual el cliente le “explica” lo que necesita, el server se lo
provee y así naliza la comunicación y el server queda liberado para “atender” al próximo
cliente.
Puede ser que el lector no tenga conocimientos básicos de redes y/o arquitectura clien-
te-servidor. Por esto, plantearemos una analogía entre un programa servidor y un alma-
cenero de barrio ya que, en realidad, trabajan de una manera muy similar.
• El almacenero abre su almacén todos los días a las 9 h y se queda paciente detrás
del mostrador esperando la llegada de un cliente.
• Cuando el cliente llega se establece un diálogo en el cual el cliente le explica al al-
macenero qué es lo que necesita comprar.
• Si mientras el almacenero está atendiendo al cliente ingresa al almacén otro cliente,
este tendrá que formar una cola y esperar a que se termine de atender al que llegó
primero.
• A medida que van llegando más clientes de los que el almacenero puede atender la
cola irá creciendo y muchos de estos preferirán ir a otro almacén donde la atención
sea más rápida y e ciente.
• Consciente de esto, el almacenero decide contratar tres empleados para que se
ocupen del despacho en el almacén.
• Ahora, cuando llega un cliente lo atenderá uno de los empleados. Cuando llega el
próximo cliente, aunque el primero no se haya retirado, será atendido por otro de los
empleados. Puede llegar un cliente más y todavía queda un empleado para atender-
lo (hayan o no terminado de ser atendidos los dos clientes anteriores).

Los hilos (threads) permiten ejecutar tareas simultáneamente.

Con este nuevo esquema, la atención en el almacén resulta ser mucho más e ciente. La
probabilidad de que se forme una gran cola es mucho menor y, en caso de formarse, el
tiempo de espera en la misma disminuye considerablemente.
Notemos que cada uno de los empleados del almacenero hace exactamente la misma
tarea: atender al cliente que llega y como son tres empleados pueden atender hasta tres
clientes simultáneamente.
Podríamos decir que cada empleado es un thread (o un “hilo”). Un thread es un proceso
que ha sido lanzado desde otro proceso (el programa) y que ejecutará una secuencia de
acciones concurrentemente a la ejecución del programa que lo lanzó.
6.2 Implementar threads en Java 207

La atención en el almacén (server) mejoró considerablemente a partir de la incorpora-


ción de empleados (threads).
Supongamos ahora que en el almacén existe una única balanza. Los tres empleados
atienden clientes concurrentemente, pero si en un momento determinado más de uno
necesita utilizar la balanza entonces tendrán que formar una cola, ya que este recurso
puede ser utilizado por un único empleado (thread) a la vez.
Esta situación demuestra que cuando multiprogramamos aparecen situaciones nuevas
que, como programadores, debemos conocer y controlar.
El ejemplo completo del servidor multithread lo estudiaremos en el capítulo de
networking (Capítulo 7).

6.2 Implementar threads en Java


En Java un thread es una clase que extiende a la clase base hread, de la cual
hereda el método run. Este método es secuencial y es allí donde debemos programar
la tarea que queremos que nuestro hilo lleve a cabo.
En el siguiente ejemplo, la clase Demo hread que extiende a hread sobrescribe el
método run y dentro de este hace dos cosas:
1. “Duerme” una cantidad aleatoria de milisegundos (entre 0 y 4999).
2. Cuando se “despierta” escribe en la pantalla el nombre que recibió como parámetro
en el constructor y muestra cuanto tiempo durmió.

package libro.cap0 ;

public class Demo hread extends hread


{
private String nombre;

public Demo hread(String nombre)


{
[Link] = nombre;
}

public void run()


{
try
{
int = (int)( [Link]()*5000);
[Link]( );
[Link]("Soy " nombre " (" ")");
}
catch(E ception e )
{
e .printStac race();
}
}
208 6 Multithreading (Hilos)

public static void main(String[] args)


{
Demo hread t1 = new Demo hread("Pedro");
Demo hread t2 = new Demo hread("Pablo");
Demo hread t = new Demo hread(" uan");

[Link]();
[Link]();
t .start();
}
}

Cuando corremos este programa, en el método main instanciamos tres Demo hread
( t1, t2 y t ) y los ejecutamos concurrentemente invocando sobre cada uno el méto-
do start. Este método invoca al método run que sobrescribimos en Demo hread.
La salida será aleatoria ya que cada hilo dormirá una cantidad diferente de milisegun-
dos, por lo tanto, el que duerma menos tiempo será el primero que escriba su nombre
en la consola. Dos corridas del programa arrojaron la siguiente salida:
Soy Pedro ( 2)
Soy uan ( 5 )
Soy Pablo ( 02 )

Soy Pablo (51 )


Soy Pedro (10 )
Soy uan ( 52 )

Tenemos que invocar al método start y este método invocará al método run .

Es muy importante tener presente que para que estos hilos sean ejecutados concurren-
temente tenemos que invocar al método start y este método invocará al método
run.
¿Qué sucederá si en lugar de invocar al método start invocamos directamente al
método run? No sucederá nada malo salvo que el programa será lineal y los métodos
run de cada hilo se ejecutarán secuencialmente en el orden en que fueron invocados,
por lo tanto, la salida del programa siempre será:
Soy Pedro ( )
Soy Pablo (y)
Soy uan (z)

en ese orden, donde x, y, z son los tiempos aleatorios que le tocó dormir a cada uno
de los hilos.

6.2.1 La interface unnable


La clase hread implementa la interface unnable de quien hereda el método run .
El ejemplo anterior podríamos replantearlo de la siguiente manera.
6.2 Implementar threads en Java 209


package libro.cap0 ;
public class Demo hread i ple ents unnable
{
private String nombre;
public Demo hread(String nombre)
{
[Link] = nombre;
}
public void run()
{
try
{
int = (int)( [Link]()*5000);
[Link]( );
[Link]("Soy " nombre " (" ")");
}
catch(E ception e )
{
e .printStac race();
}
}
public static void main(String[] args)
{
hread t1 = ne hread(ne Demo hread("Pedro"));
hread t2 = ne hread(ne Demo hread("Pablo"));
hread t = ne hread(ne Demo hread(" uan"));
[Link]();
[Link]();
t .start();
}
} ■

Ahora la clase Demo hread no extiende a hread, pero implementa la interface


unnable de la que sobrescribe el método run. Luego, en el main instanciamos tres
thread en cuyos constructores pasamos como argumento instancias de Demo hread
(es decir: instancias de unnable).
Ambas versiones del programa son equivalentes. Probablemente, para un programador
que recién comienza con el lenguaje Java la primera versión resulte más fácil de com-
prender. Sin embargo, la segunda versión (la que utiliza la interface unnable ) permite
mayor exibilidad ya que no limita la herencia de la clase.

6.2.2 Esperar a que nalice un thread


En condiciones normales decimos que un thread naliza cuando termina de ejecutar
todas las acciones programadas en su método run.
En ocasiones necesitamos esperar a que nalice un thread o un grupo de threads para
seguir adelante con otras tareas, pero como los hilos se ejecutan concurrentemente con
el programa que los lanzó tendremos el siguiente problema:
210 6 Multithreading (Hilos)


//
public static void main(String[] args)
{
hread t1 = new hread(new Demo hread("Pedro"));
hread t2 = new hread(new Demo hread("Pablo"));
hread t = new hread(new Demo hread(" uan"));
[Link]();
[Link]();
t .start();
[Link]("Final del programa ");
}
} ■

Contrariamente a lo que el lector puede intuir, la salida de este programa siempre será
"Final del Programa " seguido de los tres nombres según el tiempo aleatorio que
le haya tocado dormir a cada hilo.
Para que el mensaje de nalización del programa efectivamente salga cuando los tres
hilos hayan nalizado su método run debemos esperar a que cada uno de ellos nali-
ce. Para esto, utilizamos el método join como veremos a continuación:

//
public static void main(String[] args) t rows E ception
{
hread t1 = new hread(new Demo hread("Pedro"));
hread t2 = new hread(new Demo hread("Pablo"));
hread t = new hread(new Demo hread(" uan"));
[Link]();
[Link]();
t .start();
// esperamos por la nalizacion de los tres ilos
[Link]();
[Link]();
t .join();
[Link]("Final del programa ");
}
}

El programa principal detendrá su ejecución hasta tanto no hayan nalizado los hilos
t1, t2 y t .

6.2.3 Threads e interfaz grá ca


Cuando en una GUI alguno de los componentes da origen a un proceso que puede
llegar a demorar, tenemos que identi carlo y lanzarlo en su propio hilo de ejecución ya
que de lo contrario toda la interfaz grá ca quedará bloqueada e inutilizada mientras que
6.2 Implementar threads en Java 211

el proceso iniciado no nalice, lo que puede dar al usuario una idea del mal funciona-
miento general.
En el siguiente programa, creamos una interfaz grá ca que tiene un botón y un choice
(una de lista de donde se puede seleccionar un ítem). Cuando se presiona el botón “dor-
mimos” 10 segundos simulando que se invocó a un proceso que demora ese tiempo
(podría ser una consulta a una base de datos por ejemplo).
El lector podrá veri car (si ejecuta este programa) que una vez que se presiona el botón,
durante los siguientes 10 segundos, la GUI queda inutilizada y la sensación será de que
algo no está funcionando bien.

//
public class VentanaDemora extends Frame
{
private Button boton;
private hoice combo;
public VentanaDemora()
{
setLayout(ne FlowLayout());
add( boton = ne Button("Esto va a demorar...") );
[Link](ne EscuchaBoton());
add( combo = ne hoice() );
[Link] tem(" tem 1");
[Link] tem(" tem 2");
[Link] tem(" tem ");
setSize( 00, 00);
setVisible(true);
}
class EscuchaBoton i ple ents ActionListener
{
public void actionPerformed(ActionEvent e)
{
try
{
[Link] (10000);
[Link](" ermino la espera... ");
}
catch(E ception e )
{
e .printStac race();
thro ne untimeE ception(e );
}
}
}
public static void main(String[] args)
{
ne VentanaDemora();
}
} ■
212 6 Multithreading (Hilos)

La solución a este problema será lanzar un thread que lleve a cabo la tarea que venía-
mos haciendo en el método actionPerformed , método que se invoca cuando se
presiona el botón.

// ...
public class VentanaDemora extends Frame
{
// :
// de nicion de componentes y constructor
// :

class EscuchaBoton i ple ents ActionListener


{
public void actionPerformed(ActionEvent e)
{
// inhabilito el boton mientras dure el proceso
[Link]( alse);
// instancio y lanzo el thread que lleva a cabo la tarea
areaBoton t = ne areaBoton();
[Link]();
}
}

class areaBoton extends hread


{
public void run()
{
try
{
// hago aqui lo que antes hacia en el action er ormed
[Link](10000);
[Link](" ermino la espera... ");
// cuando nalizo la tarea vuelvo a abilitar el
// boton
[Link](true);
}
catch(E ception e )
{
e .printStac race();
thro ne untimeE ception(e );
}
}
}
// main ...
}

En esta nueva versión del programa, en el método actionPerformed instanciamos y


lanzamos un thread de la inner class areaBoton en cuyo método run hacemos lo
mismo que hacíamos antes en el método actionPerformed.
6.2 Implementar threads en Java 213

Notemos también que luego de “despertar” e imprimir el mensaje en la consola volve-


mos a habilitar el botón para que el usuario lo pueda volver a presionar.

6.2.4 Sistemas operativos multitarea


En la práctica, hoy en día todos los sistemas operativos son multitarea. Esto signi ca
que permiten ejecutar más de una aplicación al mismo tiempo dando al usuario la idea
de que todas las aplicaciones corren simultáneamente, más allá de que el hardware
tenga un único procesador.
¿Cómo puede ser que varias aplicaciones (o programas) se ejecuten simultáneamen-
te sobre un equipo que tiene un único procesador? La respuesta es simple, el uso (o
tiempo) del procesador se distribuye uniformemente entre las diferentes aplicaciones
que se están ejecutando. Esto le da al usuario una idea de que todos los programas
están abiertos y ejecutándose aunque en realidad, en un momento dado, solo un único
programa (o sección de este) puede ejecutarse a la vez.
Dentro de este entorno multitarea, la máquina virtual de Java no es más que otro progra-
ma de los múltiples que pueden correr al mismo tiempo. Los hilos que ejecutemos desde
nuestro programa serán creados como native threads (hilos nativos del sistema operativo).
Decimos que los threads que fueron instanciados y “starteados” están listos para ejecu-
tarse (en estado: ready). Estos hilos forman una cola y un proceso del sistema operativo
(el scheduler) se ocupa de tomar el primero de la cola, asignarle un quantum de tiempo
de procesador para que pueda ejecutar su método run y luego volverlo a encolar para
asignarle tiempo de procesador al próximo hilo de la cola.
Cuando un hilo está haciendo uso del tiempo de procesador decimos que está “ejecu-
tándose” o bien que su estado es: running. Cuando su tiempo naliza, el hilo vuelve al
estado ready.
Resumiendo, un thread pasa por diferentes estados durante su ciclo de vida. Entender
su funcionamiento nos ayudará a analizar situaciones más complejas de multiprogra-
mación.

6.2.5 Ciclo de vida de un thread


Un thread, desde que es instanciado y ejecutado, pasa por diferentes estados hasta
que nalmente muere. A la transición entre estos estados, se la denomina ciclo de vida
del thread. Podemos identi car los siguientes estados:
new
Thread t

start
Newed Ready

Administrado por el
notify scheduler o
notifyAll invocando al método
yield

Blocked sleep
Running Finaliza
Dead
wait el método
i/o run

Fig. 6.1 Ciclo de vida de un thread.


214 6 Multithreading (Hilos)

Cuando de nimos e instanciamos un thread decimos que está en estado “Creado” o


newed. Cuando le invocamos su método start, pasa automáticamente al estado re-
ady. El paso entre este estado y el estado running lo administra un proceso del sistema
operativo: el scheduler, o también puede suceder que el mismo thread ceda el uso del
procesador invocando al método yield. Estando en running (es decir, haciendo uso
del procesador) el thread puede llegar a ejecutar la última línea de código de su método
run con lo cual naliza su tarea y muere pasando al estado dead. También, estando en
running puede ejecutar una operación de entrada/salida o ejecutar los métodos wait
o sleep . En cualquiera de estos casos, pasará al estado “bloqueado” (blocked) y solo
saldrá de ese estado dependiendo de las siguientes situaciones:
• Si entró porque ejecutó el método wait , entonces saldrá cuando otro thread ejecu-
te el método notify o notifyAll.
• Si entró porque ejecutó el método sleep , entonces saldrá cuando nalice el tiempo
que decidió dormir.
• Si entró porque ejecutó una operación de entrada/salida, entonces saldrá cuando
esta haya nalizado.

En todos los casos, volverá al estado running para esperar a que se le vuelva a asignar
tiempo de procesador.
Probaremos lo anterior con un ejemplo muy fácil de comprender.
En el siguiente programa, de nimos una inner class que extiende a hread en cuyo
método run ejecutamos un for que itera 5 veces. Por cada iteración mostramos
el número de iteración y el nombre (que se recibe como parámetro del constructor) y
cedemos (con el método yield) el procesador al próximo thread que espera en la cola
de listos.
Luego, en el método main , instanciamos y ejecutamos dos instancias de i hread.

package libro.cap0 ;
public class Demo hread
{
public static void main(String[] args)
{
i hred t1 = new i hred("Pablo");
i hred t2 = new i hred("Pedro");
[Link]();
[Link]();
}
static class i hred extends hread
{
String nom;
public i hred(String nom)
{
t [Link] = nom;
}
6.2 Implementar threads en Java 215

public void run()


{
fo (int i=0; i< 5; i++ )
{
[Link]( nom +" - "+ i);
yield();
}
}
}
} ■

Notemos que como el método main es estático solo tiene acceso a miembros está-
ticos. Por esto, para poder instanciar la inner class dentro del método main tuvimos
que declararla como static.
Luego de ejecutar este programa, la salida será:
Pedro - 0
Pablo - 0
Pedro - 1
Pablo - 1
Pedro - 2
Pablo - 2
Pedro - 3
Pablo - 3
Pedro - 4
Pablo – 4

Lo que demuestra que el scheduler distribuye uniformemente el tiempo de procesador


entre los hilos que se están ejecutando.

6.2.6 Prioridad de ejecución


Podemos de nir en los threads mayor o menor prioridad de ejecución para que el
scheduler los favorezca o no al momento de asignarles tiempo de procesador. Para
esto, se utiliza el método setPriority que recibe valores entre Thread.MAX_PRIO-
RITY y Thread.MIN_PRIORITY (constantes que valen 10 y 1 respectivamente).
Si en el ejemplo anterior favorecemos a t1 (Pablo) asignándole la mayor prioridad,
veremos que este nalizará primero su método run porque recibirá más tiempo de
procesador que su competidor t2.

pac a e libro.cap06;

public class DemoThread3


{
public static void main(String[] args)
{
MiThred t1 = new MiThred("Pablo");
MiThred t2 = new MiThred("Pedro");
[Link](Thread.MAX_PRIORITY);
[Link](Thread.MAX_PRIORITY);
[Link](Thread.MIN_PRIORITY);
216 6 Multithreading (Hilos)

[Link]();
[Link]();
}
// :
// inner class MiThread...
// :
} ■

La salida ahora será:


Pablo - 0
Pablo - 1
Pablo - 2
Pablo - 3
Pablo - 4
Pedro - 0
Pedro - 1
Pedro - 2
Pedro - 3
Pedro – 4

6.3 Sincronización de threads


Existen ocasiones en las que dos o más hilos pueden intentar acceder a los mismos
recursos y/o datos. Para ilustrar esta situación, recordemos el ejemplo del almacenero,
sus tres empleados y su única balanza.
La balanza es un recurso compartido por los tres empleados. Si uno la está utilizando y
otro también la necesita, entonces tendrá que esperar a que el primero la deje libre ya
que de lo contrario se incurrirá en un mal uso del recurso con resultados imprevisibles.
Esta misma situación en un contexto computacional podría darse cuando dos hilos
quieren acceder a un mismo archivo para escribir información, o bien cuando dos hilos
acceden a una misma conexión de base de datos: uno podría estar ejecutando senten-
cias update mientras el otro podría ejecutar el commit y dejar en rme las sentencias
que ejecutó el primero y que, aún, no estaban veri cadas.

6.3.1 Monitores y sección crítica


El acceso a los recursos compartidos (o recursos críticos) debe ser monitoreado. El
fragmento de código que los manipula se llama sección crítica y debe ser mutuamente
excluyente, lo que signi ca que si un hilo está ejecutando su sección crítica debemos
tener la plena seguridad de que ningún otro hilo, en ese mismo momento, también la
estará ejecutando.
Java provee el modi cador synchroni ed que, aplicado a la de nición de un
método, garantiza que ese método será ejecutado (a lo sumo) por un único thread
a la vez. Esto lo veremos más adelante.
Decimos que una clase que tiene al menos un método synchroni ed es un monitor
ya que dentro de este se estará monitoreando el acceso a algún recurso crítico. Si el
método está siendo ejecutado por un thread y otro thread pretende invocarlo entonces
este último deberá ir a una cola de espera del monitor, ya que Java garantiza que un
método sincronizado solo podrá ser ejecutado por un único hilo a la vez.
6.3 Sincronización de threads 217

La sincronización es por cada instancia del monitor. Si de un monitor existen dos o más
instancias entonces, mientras un hilo está ejecutando un método sincronizado sobre
una de estas instancias otro hilo podría invocar y ejecutar el mismo método sincroniza-
do sobre otra de las instancia del monitor.

6.3.2 Ejemplo del Productor/Consumidor


El ejemplo típico para ilustrar una situación de sincronización de threads es el del Pro-
ductor/Consumidor cuyo análisis expondremos a continuación.
Supongamos que en nuestro programa tenemos dos hilos: uno (el productor) produce
caracteres y los mete en un array. El otro (el consumidor) toma caracteres del array (del
mismo array que utiliza el productor) y los muestra por pantalla.
Dado que el array tiene una capacidad nita, este podría llenarse o vaciarse según el
productor produzca caracteres más rápido de lo que el consumidor los puede consumir
o viceversa.
Si el array está lleno, entonces el productor no podrá continuar con su producción hasta
que el consumidor consuma algún carácter. Si el array está vacío, entonces el consumi-
dor no tendrá nada para consumir hasta que el productor produzca algo y lo meta en el
array.
Obviamente, además, no debería suceder que el productor acceda al array para meter
un carácter justo en el mismo momento en el que el consumidor accede para consumir.
Dado que el array es el recurso compartido al cual accederán los dos hilos debemos
monitorear su acceso a través de un monitor (una clase) con dos métodos sincroniza-
dos: poner aracter y sacar aracter. Llamaremos a esta clase onitor y su
código fuente será el siguiente:

package libro.cap0 .prodcons;

public class onitor


{
private char[] buff = null;
private int tope = 0;

private boolean lleno = false;


private boolean vacio = true;

public onitor(int capacidad)


{
buff = new char[capacidad];
}

public synchronized void poner(char c) throws E ception


{
// ientras el bu er este lleno e blo ueo ara arle la
// osibili a al onsu i or e onsu ir algun ara ter
while( lleno )
{
wait();
}
218 6 Multithreading (Hilos)

// seccion critica
buff[ tope] = c;

vacio = false;
lleno = tope =[Link];

notifyAll();
}

public synchronized char sacar() throws E ception


{
// mientras e bu er este vacio me b o ueo ara dar e a
// osibi idad a roductor de roducir a gun caracter
while( vacio )
{
wait();
}

// seccion critica
char c = buff[ tope];

lleno = false;
vacio = tope =0;

notifyAll();

return c;
}
} ■

En el método poner , lo primero que hacemos es preguntar por el estado del buffer
(el array). Si está lleno, entonces no podemos hacer nada y mientras esto siga así nos
bloqueamos para darle la posibilidad al consumidor de que pueda consumir un carác-
ter y así desagotar el buffer. Cuando nos desbloqueamos (ya veremos cuándo y cómo
sucederá) nos encontraremos nuevamente dentro del while y si todo sigue igual nos
volveremos a bloquear. Así hasta que la variable lleno sea false . En ese momento
saldremos del while y ejecutaremos las líneas posteriores en las que, como accede-
mos a las variables compartidas decimos que constituyen la sección crítica.
Al nal invocamos al método notifyAll para pasar a ready a todos los hilos que
están bloqueados.
El análisis del método sacar es análogo al análisis del método poner .
Las clases Productor y onsumidor extienden a hread. Ambas reciben como
parámetro en el constructor una instancia del monitor (o buffer).
Comencemos analizando el código del productor, quien produce caracteres consecu-
tivos contando a partir del carácter A . La cantidad de caracteres que va a producir
dependerá del parámetro n que recibe en el constructor. Luego de meter cada carácter
en el buffer duerme una cantidad sleep de milisegundos, valor que también recibe
como parámetro en el constructor.
6.3 Sincronización de threads 219


package libro.cap0 .prodcons;

public class Productor extends hread


{
private onitor buff;
private int n;
private int sleep;

public Productor( onitor b, int n, int s)


{
// el monitor
[Link] = b;

// cuantos caracteres debe producir


this.n = n;

// cuanto tiempo dormir entre caracter y caracter


[Link] = s;
}

public void run()


{
try
{
char c;

or(int i=0; i n; i )
{
c = (char) ( A i);

[Link](c);

[Link]("Produje " c);

sleep( (int)( [Link]()*sleep));


}
}
catch(E ception e )
{
e .printStac race();
thro ne untimeE ception(e );
}
}
} ■

El análisis de la clase onsumidor es análogo al de la clase Productor. El código


es el siguiente.
220 6 Multithreading (Hilos)


package libro.cap0 .prodcons;
public class onsumidor extends hread
{
private onitor buff;
private int n;
private int sleep;
public onsumidor( onitor b, int n, int s)
{
[Link] = b;
this.n = n;
[Link] = s;
}
public void run()
{
try
{
char c;
or(int i=0; i n; i )
{
c = [Link]();
[Link](" onsumi " c);
sleep( (int)( [Link]()*sleep));
}
}
catch(E ception e )
{
e .printStac race();
thro ne untimeE ception(e );
}
}
}

Por último, veremos el código de un programa que instancia un onitor , un


Productor y un onsumidor y los pone a trabajar.

package libro.cap0 .prodcons;
public class est
{
public static void main(String[] args)
{
onitor m = ne onitor( );
Productor p = ne Productor(m, ,2000);
onsumidor c = ne onsumidor(m, , 000);
[Link]();
[Link]();
}
}

6.4 Resumen 221

En este ejemplo instanciamos un monitor que controlará el acceso a un array con una
capacidad de 3 caracteres. El productor producirá 6 caracteres, cada uno con una
demora de a lo sumo 2000 milisegundos (o 2 segundos). El consumidor consumirá 6
caracteres del mismo buffer con una demora entre carácter y carácter de 4000 milise-
gundos (4 segundos). Es decir, el productor produce más rápido de lo que consume el
consumidor.
La salida de una corrida de este programa podría ser:
r u
n u
r u
r u
n u
r u
r u
r u
n u
n u
n u
n u

Obviamente, el buffer nunca tendrá más de tres caracteres y los dos hilos trabajan sin-
cronizados entre sí para utilizar y compartir el recurso de uso común: el array.

6.4 Resumen
En este capítulo estudiamos cómo disparar hilos que ejecuten procesos concurrentes.
Esto nos permitirá, más adelante, desarrollar un servidor multitarea en el cual podremos
exponer a través de la red los servicios de la aplicación que analizamos en el Capítulo 4.
Claro que para esto primero tendremos que estudiar cómo comunicar procesos a través
de la red, conceptos de comunicaciones y arquitectura cliente/servidor. Todo esto es lo
que estudiaremos en el próximo capítulo.

También podría gustarte