Hilos en Java
Hilos en Java
Contenidos Objetivos
2.1. INTRODUCCIÓN
En el capítulo anterior se estudió la programación concurrente y cómo se podían realizar
programas concurrentes con el lenguaje Java. Se hizo una breve introducción al concepto de hilo
y las diferencias entre estos y los procesos.
Recordemos que los hilos comparten el espacio de memoria del usuario, es decir, corren
dentro del contexto de otro programa; y los procesos generalmente mantienen su propio espacio
de direcciones y entorno de operaciones. Por ello a los hilos se les conoce a menudo como
procesos ligeros.
En este capítulo usaremos los rulos en Java para realizar programas concurrentes.
Podemos usar los hilos para diferentes aplicaciones: para realizar programas que tengan que
realizar varias tareas simultáneamente, en los que la ejecución de una parte requiera tiempo y no
deba detener el resto del programa. Por ejemplo, un programa que controla sensores en una
fábrica, cada sensor puede ser un hilo independiente y recoge un tipo de información; y todos
deben controlarse de forma simultánea. Un programa de impresión de documentos debe seguir
funcionando, aunque se esté imprimiendo un documento, tarea que se puede llevar por medio de
un hilo. Un programa procesador de textos puede tener un hilo comprobando la gramática del
texto que estoy escribiendo y otro hilo guardando el texto en disco cada cierto tiempo. En un
programa de bases de datos un hilo pinta la interfaz gráfica al usuario. En un servidor web, un
hilo puede atender las peticiones entrantes y crear un hilo por cada cliente que tenga que servir.
}//PrimerHilo
En el siguiente ejemplo se crea una clase que extiende Thread. Dentro de la clase se defrnen
el constructor, el método runO con la funcionalidad que realizará el hilo y el método mainO
donde se crearán 3 hilos. La misión del hilo, descrita en el método runO, será visualizar un
mensaj e donde se muestre el nombre del hilo que se está ejecutando y el contenido de un
contador. Se utiliza una variable para mostrar el nombre del hilo que se ejecuta, esta variable se
pasa al constructor y éste se lo pasa al constructor de la clase base Thread mediante la palabra
reservada super, para acceder a este nombre se usa el método getNameO. Desde el método
mainO se crean los hilos y para iniciar cada hilo usamos el método startO:
public class HiloEjemplol extends Thread
//constructor
public HiloE j emplol(String nombre)
super(nombre) ;
System.out.println( " CREANDO HILO:"+ getName());
70 Programación de servicios y procesos
11 mé todo run
public void run()
for (int i=O ; i<S; i++)
System .out.pr intln( "Hilo :" + qetName() + " C " + i);
11
public static void main(Strinq[] arqs) {
HiloEjemplo1 h1 new HiloEjemplo1("Hilo 1 " ) ;
HiloEjemplo1 h2 new HiloEjemplo1("Hilo 2 " ) ;
HiloE j emplo1 h3 new HiloEjemplo1("Hilo 3 " ) ;
h1 . start () ;
h2. s tart () ;
h3. start () ;
}// HiloEjemplo1
Es muy típico ver dentro del método runO un bucle infinito de forma que el hilo no termina
nunca (más adelante veremos cómo detener el hilo). La ejecución del ejemplo anterior no
siempre muestra la misma salida, en este caso se puede observar que los hilos no se ejecutan en
el orden en que se crean:
CREANDO HILO:Hilo 1
CREANDO HILO : Hilo 2
CREANDO HILO:Hilo 3
3 HILOS INICIADOS ...
Hilo : Hilo 1 e o
Hilo : Hilo 1 e 1
Hilo : Hilo 1 e 2
Hilo : Hilo 1 e 3
Hilo : Hilo 1 e 4
Hilo : Hilo 3 e o
Hilo : Hilo 2 e o
Hilo : Hilo 3 e 1
Hilo : Hilo 3 e 2
Hilo : Hilo 3 e 3
Hilo : Hilo 3 e 4
Hilo : Hilo 2 e 1
Hilo : Hilo 2 e 2
Hilo : Hilo 2 e 3
Hilo: Hilo 2 e 4
En este ejemplo se ha incluido el método mainO dentro de la clase hilo. Podemos definir por
un lado la clase hilo y por otro la clase que usa el hilo, tendríamos dos clases, la que extiende
Thread, HiloEjemplol_ V2java:
public class HiloEjemplol V2 extends Thread {
11 constructor -
public HiloEjemplo1_V2(String nombre) {
super(nombre);
System . out . println( " eREANDO HILO :"+ getName());
}
11 r::é todo run
Capítulo 2. Programación multihilo 71
}// HiloEjemplol_V2
hl. start () ;
h2. start () ;
h3. start ();
Se compila primero la clase hilo y después la que usa el hilo, se ejecuta la clase que usa el
hilo:
D:\CAPIT2>javac HiloEjemplol_V2 . java
D:\CAPIT2>javac UsaHiloEjemplol_ V2 . java
D:\CAPIT2>java Us aHi loE jemplol_V2
En la siguiente tabla se muestran algunos métodos útiles sobre los hilos, algunos ya se han
usado:
MÉTODOS MISIÓN
Hace que el hilo comience la ejecución; la máquina virtual de Java llama
startO al método runO de este hilo.
boolean isAiiveO Comprueba si el hilo está vivo
Hace que el hilo actualmente en ejecución pase a donnir temporalmente
sleep(long mils) durante el número de milisegundos especificado. Puede lanzar la
excepción lnterruptedException.
Constituye el cuerpo del hilo. Es llamado por el método startO después
de que el hilo apropiado del sistema se haya inicializado. Si el método
runO runO devuelve el control, el hilo se detiene. Es el único método de la
interfaz Runnable.
Devuelve una representación en fonnato cadena de este hilo, incluyendo
String toStringO el nombre del hilo, la prioridad, y el grupo de hilos. Ejemplo:
Thread[HILO 1,2,main]
long getldO Devuelve el identificador del hilo.
Hace que el hilo actual de ejecución pare temporalmente y pennita que
void yieldO
otros hilos se ejecuten.
String getNameO Devuelve el nombre del hilo.
Cambia el nombre de este hilo, asignándole el especificado como
setName(String name)
argumento.
n Programación de servicios y procesos
MÉTODOS MISIÓN
HiloEjemplo2 h = null ;
}//
La ejecución muestra la siguiente salida (que puede variar de una ejecución a otra), en la que
podemos observar que el método toStringO devuelve un string que representa al hilo:
Thread[nombre del hilo. la prioridad, grupo de hilos], el método currentTh readO que devuelve
una referencia al objeto hilo actualmente en ejecución y activeCountO que devuelve el número
de hilos activos actualmente dentro del grupo:
Principal
Thread[Principal,5 , ma in]
Informacion del HILOO : Thread[HIL00 , 1 , ma in]
I nforma c ion del HI L01 : Thread [HIL01, 2 ,main ]
Dentro de l Hilo HI LOO
Prioridad 1
ID : 10
Hilos activos : 3
I n f ormacion del HIL02 : Thread[H I L02 , 3 , ma in)
3 HILOS CREADOS ...
Hilos acti vos : 4
Dentro del Hilo HIL02
Prioridad 3
ID : 12
Hi los activo s: 3
Dent ro del Hi l o HIL01
Prioridad 2
ID 11
Hilos activos : 2
Todo hilo de ejecución en Java debe formar parte de un grupo. Por defecto, si no se especifica
ningún grupo en el constructor, los hilos serán miembros del grupo main, que es creado por el
sistema cuando arranca la aplicación Java.
La clase ThreadGroup se utiliza para manejar grupos de hilos en las aplicaciones Java. La
clase T hread proporciona constructores en los que se puede especificar el grupo del hilo que se
está creando en el mismo momento de instanciado. El siguiente ejemplo crea un grupo de hilos
de nombre Grupo de hilos. A continuación, crea tres hilos usando el siguiente constructor de la
clase T hread :
Thread (grupo Th r e adGroup, destino Runnabl e, nombre String)
En el que se especifica el grupo de hilos, el objeto hilo y el nombre del hilo. El código es el
siguiente:
public c lass HiloEjempl o2Grupos extends Thre ad {
publ ic void run() {
System. out . p rintln( " I nforma cion del hilo : " +
Thread.currentThread() . toString()) ;
fo r (int i = O; i < 1000 ; i++) i++ ;
System . out . println(Thread . currentThr ead() . getName() +
"Finalizando la ejecuci ó n." ) ;
}
11
74 Programación de servicios y procesos
hl. start () ;
h2. start () ;
h3. start ();
ACTIVIDAD 2.1
Crea dos clases (hilos) Java que extiendan la clase Thread . Uno de los hilos debe visualizar
en pantalla en un bucle infinito la palabra TIC y el otro hilo la palabra TAC. Dentro del bucle
utiliza el método sleepO para que nos de tiempo a ver las palabras que se visualizan cuando lo
ejecutemos, tendrás que añadir un bloque try-catch (para capturar la excepción
InterruptedException). Crea después la función mainO que haga uso de los hilos anteriores. ¿Se
visualizan los textos TIC y TAC de forma ordenada (es decir TICTAC TICTAC ... )?
Realiza el Ejercicio l.
}//
}//PrimerHiloR
//Segundo hilo
PrimerHiloR hilo2 = new PrimerHiloR();
Thread hilo= new Thread(hilo2);
hilo.start();
//Tercer Hilo
new Thread(new PrimerHiloR()) . start();
}//UsaPrimerHiloR
ACTIVIDAD 2.2
Transforma el Ejercicio 1 usando la interfaz Runnable para declarar el hilo. Después realiza
el programa Java que pide el enunciado del ejercicio.
Realiza el Ejercicio 2.
76 Programación de servicios y.procesos
Seguidamente vamos a ver cómo usar un hilo en un applet para realizar una tarea repetitiva,
en el ejemplo la tarea será mostrar la hora con los minutos y segundos: HH:MM:SS; véase
Figura 2.2; normalmente la tarea repetitiva se encierra en un bucle infmito. Un applet es una
aplicación Java que se puede insertar en una página web; cuando el navegador carga la página, el
applet se carga y se ejecuta. Nuestro applet implementará la interfaz Runnable, por tanto debe
incluir el método runO con la tarea repetitiva.
[iJ V&SOr de Applet Reloj.dau o X
Applet
18:48:47
Applet inidado.
Cuando el applet necesita matar el hilo le asigna el valor nu/1, esta acción se realiza en el
método stopO del applet (se recomienda en los applets que implementan Runnable). Es una
forma más suave de detener el hilo que utilizar el método stopQ del hilo (h ilo.stopO), ya que este
puede resultar peligroso. El código del método runO es el siguiente:
p ublic void run() {
Thread h i loActual = Thread . c urrentThread{) ;
whi l e (hil o == hi l o Actual ) {
11 tare a repet i t iva
}
try {
Thread.sleep (1 000 );
} catch (Inte r r up t edExc ept i o n e) {}
public void s t op ()
hilo = null ;
El método repaintO se utiliza para actualizar el applet cuando cambian las imágenes
contenidas en él. Los applets no tienen método mainO, para ejecutarlos necesitamos crear un
fichero HTML, por ejemplo creamos el fichero Reloj.html con el siguiente contenido:
<html >
<applet cod e ="Reloj.class" wi d t h= " 2 00 " heigh t= " l OO" >
</applet>
</h tml>
Desde un entorno gráfico como Eclipse no sería necesario crear el HTML. Para compilarlo y
ejecutarlo desde la línea de comandos usamos el comando appletviewer, se visualizará una
ventanita similar a la de la Figura 2.2:
D: \CAPIT2>javac Reloj . j a va
D: \CAPIT2>appletviewer Reloj . html
Se usan las clases Calendar y SimpleDateFormat para obtener la hora y darle formato. En el
método paintO del applet se han utilizado los siguientes métodos:
• clearRect (int x, int y, int ancho, int alto): borra el rectángulo especificado
rellenándolo con el color de fondo de la superficie de dibujo actual.
• setBackground(Color e): establece el color de fondo .
• setFont(Fontfuente): especifica la fuente .
• dra wString(String texto, int x, int y): pinta el texto en las posiciones x e y .
En el siguiente ejemplo se crea un hilo que irá incrementando en 1 un contador, el contador se
inicializa en O. Se definen dos botones. El primero, Iniciar contador, crea el hilo, al pulsarle
cambia el texto a Continuar y empieza a ejecutarse el hilo. El botón Parar contador hace que el
hilo se detenga y deje de contar, finaliza el método r unQ. Al pulsar de nuevo en Continuar el
contador continúa incrementándose a partir del último valor, se lanza un nuevo hilo. La Figura
2.3 muestra un momento de la ejecución.
_..
¡¡¡y...._ - o X
_.. o X
~--1 1 e- J
Pont CXIIItldorl p--1
o 24
....:~~pltt;,;.;_;.;;.
·,;;;;;.
· _ _ __¡clopjlj:.::"':::;lnid=aciO.=------l
El applet implementa las interfaces Runnable y ActionListener. Esta última se usa para
detectar y manejar eventos de acción, como por ejemplo hacer clic con el ratón en un botón.
Posee un solo método actionPerformed(ActionEvent e) que es necesario implementar.
En el método initO del applet se añaden los botones con el método add.O: add ( bl = new
Button("Iniciar contador'')); y con el método addActionListenerO añadimos el listener para
que detecte cuando se hace clic sobre el botón: bl.addActionListener(this); se usa this, ya que es
la clase la que implementa la interfaz.
Al pulsar uno de los botones se invocará al método actionPerformed(A ctionEvent e) donde
se analizará el evento que ocurre, la pulsación de un botón o del otro. Dentro del método se
comprueba el botón que se ha pulsado. Si se ha pulsado el botón bl se cambia la etiqueta del
botón y se comprueba si el hilo es distinto de nulo y está corriendo, en este caso no se hace nada;
y si el hilo es nulo entonces se crea y se inicia su ejecución. Si se pulsa el segundo botón, b2, se
utiliza la variable booleana parar asignándole valor true para controlar que no se incremente el
contador:
public void actionPerformed(ActionEvent e) {
if(e . getSource() == bl) //Pulso botón Iniciar contador o Conti nuar
{
b l. setLabel ("Continuar " ) ;
if(h != null && h . isAlive()) {} //Si el hilo e s tá cor riendo
//no hago nada .
e lse {
//creo hilo la pri mera vez y cuando finaliz a el método run
h = new Thre ad (thi s);
h . start() ;
}//actíonPerformed
repaint() ;
CONTADOR++;
}//actionPerformed
ACTIVIDAD 2.3
Partiendo del ejemplo anterior separa el hilo en una clase aparte dentro del applet que
extienda Thread. El applet ahora no implementará Runnable, debe quedar así:
public class actividad2_ 3 extends Applet implement s ActionList ener {
class Hi loContador extends Thread {
//atributos y métodos
}//fin c lase
//atributos y métodos
}//fin actividad2 3
Capitulo 2. Programación multihilo 81
Se debe crear un applet que lance dos hilos y muestre dos botones para finalizarlos, Figura
2.4.
[dv-os._ O X
Applet
ltfu~~.!,lj!!HJI
Fmalizar Hilo 2 1
Hilo1: 101
Hilo2: 121
Applet ínidaóo.
Define en la clase HiloContador un constructor que reciba el valor inicial del contador a partir
del cual empezará a contar; y el método getContadorO que devuelva el valor actual del contador.
El applet debe crear e iniciar 2 hilos de esta clase, cada uno debe empezar con un valor. Mostrará
2 botones, uno para detener el primer hilo y el otro el segundo, Figura 2.4. Para detener los hilos
usa el método stopO: hilo.stopO (veremos más adelante que este método está en desuso y no se
debe usar). Cambia el texto de los botones cuando se pulsen, que muestre Finalizado Hilo 1 o 2
dependiendo del botón pulsado.
En el método initO prepara la pantalla. En el método startO inicia los dos hilos. En el método
paintO pinta la pantalla. En el método actionPerformed(ActionEvent e) controla los botones y
en el método stopO fmaliza los hilos asignándoles el valor nu/1.
}//
}//HiloEjemploDead
• Blocked (Bloqueado): en este estado podría ejecutarse el hilo, pero hay algo que lo
evita. Un hilo entra en estado bloqueado cuando ocurre una de las siguientes
acciones:
l. Alguien llama al método sleepO del hilo, es decir, se ha puesto a dormir.
2. El hilo está esperando a que se complete una operación de entrada/salida.
3. El hilo llama al método waitO. El hilo no se volverá ejecutable hasta que reciba
los mensajes notifyO o notifyAIJO.
4. El hilo intenta bloquear un objeto que está actualmente bloqueado por otro rulo.
5. Alguien llama al método suspendO del hilo. No se volverá ejecutable de nuevo
hasta que reciba el mensaje resumeO.
El método getStateO devuelve una constante que indica el estado del hilo. Los valores son los
siguientes:
• NEW: El hilo aún no se ha iniciado.
• RUNNABLE: El hilo se está ejecutando.
• BLOCKED: El hilo está bloqueado, esperando tomar el bloqueo de un objeto.
• W AlTING: El hilo está esperando indefmidamente hasta que otro realice una acción.
Por ejemplo un hilo que llama al método waitO de un objeto, está esperando hasta
que otro hilo llame al método notify0 del objeto.
• TIMED_W AITING: El hilo está esperando que otro hilo realice una acción un
tiempo de espera especificado.
• TERMINATED: El hilo ha fmalizado.
1
Figura obtenida del libro Core Java™ 2: Volume II- Advanced Features. Cay S. Horstmann, Gary Comell.
84 Programación de servicios y procesos
suspender.esperandoParaReanudar() ; //comprobar
Capitulo 2. Programación multihilo 85
El método waitO sólo puede ser llamado desde dentro de un método sincronizado
(synchronized). Estos tres métodos: waitQ, notifyO y notifyAliQ, se usan en sincronización de
hilos. Forman parte de la clase Object, y no parte de T hread como es el caso de sleepQ,
suspendO y resumeQ. Se tratarán más adelante.
ACTIVIDAD 2.4
Partimos de las clases anteriores MyHilo y SolicitaSuspender. Vamos a modificar la clase
MyHilo. Se define una variable contador y se inicia con valor O. En el método r unO y dentro de
un bucle que controle el fin del hilo mediante una variable, se incrementa en 1 el valor del
contador y se visualiza su valor, se incluye también un sleepO para que podamos ver los
números. Haz una llamada al método esperandoParaReanudarO para suspender el hilo, el
sleepO lo podemos hacer antes o después. Crea en la clase un método que devuelva el valor del
contador. Al finalizar el bucle visualiza un mensaje.
Para probar las clases crea un método mainO, en el que introducirás una cadena por teclado en
un proceso repetitivo hasta introducir un*. Si la cadena introducida es S se suspenderá el hilo, si
la cadena es R se reanudará el hilo. El hilo se lanzará solo una vez después de introducir la
primera cadena. Al finalizar el proceso repetitivo visualizar el valor del contador y finalizar el
hilo. Comprueba que todos los mensajes se visualicen correctamente.
Realiza el Ejercicio 8.
Este método al igual que suspendO, resumeO y destroyO han sido abolidos en Java 2 para
reducir la posibilidad de interbloqueo. El método runO no libera los bloqueos que haya
adquirido el hilo, y si los objetos están en un estado inconsistente los demás hilos podrán verlos y
modificarlos en ese estado. En lugar de usar este método se puede usar una variable como se vio
en el estado Dead del hilo.
El método isAiiveO devuelve true si el hilo está vivo, es decir ha llamado a su método runO y
aún no ha terminado su ejecución o no ha sido detenido con stopO; en caso contrario devuelve
false. En ejemplos anteriores vimos cómo se usaba este método.
El método interruptO envía una petición de interrupción a un hilo . Si el hilo se encuentra
bloqueado por una llamada a sleepO o waitO se lanza una excepción JnterruptedException. El
método islnterruptedO devuelve true si el hilo ha sido interumpido, en caso contrario devuelve
fa/se. El siguiente ejemplo usa interrupciones para detener el hilo. En el método runO se
comprueba en el bucle while si el hilo está interrumpido, si no lo está se ejecuta el código. El
método interrumpirO ejecuta el método interruptO que lanza una interrupción que es recogida
por el manejador (catch):
public cla ss Hil oEj emplointerrup extends Thread {
public void run () {
try {
whi le (!isin t e r rupted() )
System . out . println("En el Hilo" );
Thread . s leep (lO) ;
catch (InterruptedException e) {
System . out . println("HA OCURRIDO UNA EXCEPCIÓN " );
Si en el código anterior quitamos la linea Thread.sleep(JO); también hay que quitar el bloque
try-catcb, la interrupción será recogida por el método islnterr uptedO, que será true con lo que
la ejecución del hilo terminará ya que fmaliza el método runO.
El método joinO provoca que el hilo que hace la llamada espere la finalización de otros hilos.
Por ejemplo si en el hilo actual escribo hl.joinO, el hilo se queda en espera hasta que muera el
Capítulo 2. Programación multihilo 87
hilo sobre el que se realiza el joinQ, en este caso hl. En el siguiente ejemplo el método runO de
la clase HiloJoin visualiza en un bucle for un contador que empieza en 1 hasta un valor n que
recibe el constructor del hilo:
class HiloJoin extends Thread {
p rivat e int n ;
public HiloJoin{String nom, int n) {
s uper{nom) ;
this . n=n ;
Si en el ejemplo anterior quitamos los joinQ veremos que el texto FINAL DE PROGRAMA
no se mostrará al final. El método joinO puede lanzar la excepción lnterruptedException, por
ello se incluye en un bloque try-catch.
ACTIVIDAD 2.5
Modifica el applet de la Actividad 2.3 de manera que la finalización de los hilos no se realice
con el método stopQ sino que se realice de alguna de las formas seguras vistas anteriormente
(usando interrupciones o usando una variable para controlar el fin del hilo).
Realiza el ejercicio 9.
hl.setPriority(Thread.NORM_PRIORITY);
h2.setPriority(Thread.MAX_PRIORITY);
h3 . setPriority(Thread . MIN_ PRIORITY);
hl. start () ;
h2 . start ();
h3. start () ;
try {
Thread .sleep(10000);
}catch (Exception e) { }
hl . pararHilo() ;
h2 .pararHilo{) ;
h3.pararHilo() ;
Varias ejecuciones del programa muestran las siguientes salidas, se puede observar que el
máximo valor del contador lo obtiene el objeto hilo con prioridad máxima, y el mínimo el de
prioridad mínima:
Pero no siempre ocurre esto. Podemos encontrar la siguiente salida en la que se observa que
los valores de los contadores no dependen de la prioridad asignada al hilo:
h2 (Prioridad Maxima) : 4822
Fin hilo Hilo3
Fin hilo Hilo2
Fin hilo Hilol
hl (Prioridad Normal) : 4823
h3 (Prioridad Mínima) : 4823
En el siguiente ejemplo se asignan diferentes prioridades a cada uno de los hilos de la clase
EjemploHiloPrioridad2 que se crean. En la ejecución se puede observar que no siempre el hilo
con más prioridad es el que antes se ejecuta:
public class EjemploBiloPrioridad2 extends Thread {
EjemploBiloPrioridad2(String nom) {
this .setName(nom) ;
//asignamos prioridad
h1 osetPriority(Thread . MIN_PRIORITY) ;
h2 . setPriority(3) ;
h3 . setPriority(Thread . NORM_PRIORITY ) ;
h4 . setPriority(7) ;
h5osetPriority(Thread.MAX_ PRIORITY);
ACTIVIDAD 2.6
Prueba los ejemplos anteriores variando la prioridad y el orden de ejecución de cada bjlo.
Comprueba los resultados para el primer ejemplo y para el segundo.
Realiza el ejercicio 6
Para probar el objeto compartido se definen dos clases que extienden Thread. En la clase
HiloA se usa el método del objeto contador que incrementa en uno su valor. En la clase HiloB se
usa el método que decrementa su valor. Se añade un sleepQ intencionadamente para probar que
un hilo se duerma y mientras el otro haga otra operación con el contador, así la CPU no realiza
de una sola vez todo un hilo y después otro y podemos observar mejor el efecto:
class BiloA extends Thread {
private Contador contador ;
publ ic HiloA(String n, Contador e) {
setName(n);
contador = e ;
Nos puede dar la impresión que al ejecutar los hilos el valor del contador en el hilo A debería
ser 400, ya que empieza en 100 y le suma 300; y en B, 100 ya que se resta 300; pero no es así. Al
ejecutar el programa los valores de salida pueden no ser Jos esperados y variará de una ejecución
a otra:
~iloB contador vale 100
3iloA contado r v a le 100
Los métodos runO de las clases HiloA e HiloB se pueden sustituir por los siguientes; para el
HiloB:
public void run( ) {
synchro nized (contador) {
for (int j = O; j < 300; j++)
contador.decrementa();
Para el HiloA:
public void run ()
synchronized (contador) {
for (int j = O; j < 300 ; j++)
contador.incrementa();
El bloque synchronized o región crítica (que aparece sombreado) lleva entre paréntesis la
referencia al objeto compartido Contador. Cada vez que un hilo intenta acceder a un bloque
sincronizado le pregunta a ese objeto si no hay algún otro hilo que ya le tenga bloqueado. Es
decir, le pregunta si no hay otro hilo ejecutando algun bloque sincronizado con ese objeto. Si está
tomado por otro hilo, entonces el hilo actual se suspende y se pone en espera hasta que se libere
el bloqueo. Si está libre, el hilo actual bloquea el objeto y ejecuta el bloque; el siguiente hilo que
intente ejecutar un bloque sincronizado con ese objeto, será puesto en espera. El bloqueo del
objeto se libera cuando el hiJo que lo tiene tomado sale del bloque porque termina la ejecución,
ejecuta un retum o lanza una excepción.
La compilación y ejecución muestra la siguiente salida (se ha guardado el ejemplo anterior
con los cambios en Compartirlnfljava):
Capitulo 2. Programación multihilo 95
ACTIVIDAD 2.7
Crea un programa Java que lance cinco hilos, cada uno incrementará una variable contador de
tipo entero, compartida por todos, 5000 veces y luego saldrá. Comprobar el resultado fmal de la
variable. ¿Se obtiene el resultado correcto? Ahora sincroniza el acceso a dicha variable. Lanza
los hilos primero mediante la clase T hread y luego mediante el interfaz Runnable. Comprueba
el resultado.
valor del saldo, otro, resta al saldo una cantidad y el tercero realiza las comprobaciones para
hacer la retirada de dinero, es decir que el saldo actual sea >= que la cantidad que se quiere
retirar; el constructor inicia el saldo actual. También se añade un sleepQ intencionadamente para
probar que un hilo se duerma y mientras el otro haga las operaciones:
class Cuenta {
prívate int saldo ;
A continuación, se crea la clase SacarDinero que extiende Thread y usa la clase Cuenta para
retirar el dinero. El constructor recibe una cadena, para dar nombre al hilo; y la cuenta que será
compartida por varias personas. En el método runO se realiza un bucle donde se invoca al
método RetirarDineroO de la clase Cuenta varias veces con la cantidad a retirar, en este caso
siempre es 1O, y el nombre del hilo:
clas s SacarDinero extends Thread {
prívate Cuenta e ;
String nom;
public SacarDi nero(String n , Cuenta e ) {
super (n) ;
this . c = e;
}// run
Capftulo 2. Programación multihilo 97
Por último se crea el método mainO. donde primero se deftne un objeto de la clase Cuenta y
se le asigna un saldo inicial de 40. A continuación se crean dos objetos de la clase SacarDinero,
imaginemos que son las dos personas que comparten la cuenta, y se inician los hilos:
public class Comparti rinf3 {
publie statie void main(String[] args) {
Cuenta e= new Cuenta(40};
Saea rDinero h1 new SaearDinero( " Ana " , e};
SaearDinero h2 = new SaearDinero( " Juan ", e } ;
h1. start (} ;
h2 . start (} ;
Para evitar esta situación la operación de retirar dinero, método RetirarDineroO de la clase
Cuenta, debería ser atómica e indivisible, es decir si una persona está retirando dinero, la otra
debería ser incapaz de retirarlo hasta que la primera haya realizado la operación. Para ello
declaramos el método como synchronized. Sincronizar métodos permite prevenir
inconsistencias cuando un objeto es accesible desde distintos hilos: si un objeto es visible para
más de un hilo, todas las lecturas o escrituras de las variables de ese objeto se realizan a través de
métodos sincronizados.
Cuando un hilo invoca un método synchronized, trata de tomar el bloqueo del objeto a que
pertenezca. Si está libre, lo toma y se ejecuta. Si el bloqueo está tomado por otro hi lo se suspende
el que invoca hasta que aquel finalice y libere el bloqueo. La forma de declararlo es la siguiente:
s ynchronized pub~i c void metodo() {
//instrucciones atómicas . ..
O bien:
pub~ic synchronized void metodo() {
//instrucciones atómicas .. .
La ejecución mostraría la siguiente salida, recuerda que de una ejecución a otra puede variar,
pero en este caso el saldo no será negativo:
Ana: SE VA A RETIRAR SALDO (ACTUAL ES : 40)
Ana retira =>10 ACTUAL(30)
Juan : SE VA A RETIRAR SALDO (ACTUAL ES: 30)
Juan retira =>10 ACTUAL(20)
Ana : SE VA A RETIRAR SALDO (ACTUAL ES: 20)
Ana retira =>10 ACTUAL(10)
Ana: SE VA A RETIRAR SALDO (ACTUAL ES: 10)
Ana retira =>10 ACTUAL(O)
J uan No puede retirar dinero, NO HAY SALDO(O)
Juan No puede retirar dinero, NO HAY SALDO(O)
Juan No puede retirar dinero, NO HAY SALDO(O)
Ana No puede retirar dinero, NO HAY SALDO(O)
Se debe tener en cuenta que la sincronización disminuye el rendimiento de una aplicación, por
tanto, debe emplearse solamente donde sea estrictamente necesario.
ACTIVIDAD 2.8
Crea una clase de nombre Saldo, con un atributo que nos indica el saldo, el constructor que da
un valor inicial al saldo. Crea varios métodos uno para obtener el saldo y otro para dar valor al
saldo, en estos dos métodos añade un sleepO aleatorio. Y otro método que reciba una cantidad y
se la añada al saldo, este método debe informar de quién añade cantidad al saldo, la cantidad que
añade, el estado inicial del saldo (antes de añadir la cantidad) y el estado final del saldo después
de añadir la cantidad. Defme los parámetros necesarios que debe de recibir este método y
definelo como synchronized.
Crea una clase que extienda Thread, desde el método runO hemos de usar el método de la
clase Saldo que añade la cantidad al saldo. Averigua los parámetros que se necesita en el
constructor. No debe visualizar nada en pantalla.
Crea en el método mainO un objeto Saldo asignándole un valor inmcial. Visualiza el saldo
inicial. Crea varios hilos que compartan ese objeto Saldo. A cada hilo le damos un nombre y le
asignamos una cantidad. Lanzamos los hilos y esperamos a que finalicen para visualizar el saldo
fmal del objeto Saldo. Comprueba los resultados quitando synchronized del método de la clase
Saldo que reciba la cantidad y se la añada al saldo.
Realiza el Ejercicio 11.
}// ObjetoCompartido
Capitulo 2. Programación multihilo 99
Para usarla definimos un método mainO en el que se crea un objeto de esa clase que además
será compartido por dos hilos del tipo HiloCadena. Los hilos usarán el método del objeto
compartido para pintar una cadena, esta cadena es enviada al crear el hilo (new HiloCadena
(objeto compartido, cadena)):
public class BloqueoHilos {
public static void main(String[] args) {
ObjetoCompartido com = new ObjetoCompartido();
HiloCadena a new HiloCadena (com, " A " );
HiloCadena b = new HiloCadena (eom, " B " ) ;
a.start();
b . start ();
}//BloqueoHi los
}//HiloCadena
Se pretende mostrar de fonna alternativa los String que inicializa cada hilo y que la salida
generada al ejecutar la función mainO sea la siguiente: "A B A B A B .... ". Parece que una
primera aproximación para solucionarlo sería sincronizar el trozo de código que hace uso del
objeto compartido (dentro del método runO):
synchronized (objeto) {
for (int j = O; j < 10; j++)
objeto.PintaCadena(cad);
Pero al ejecutarlo, la salida no es la esperada ya que la sincronización evita que dos llamadas
a métodos o bloques sincronizados del mismo objeto se mezclen; pero no garantiza el orden de
las llamadas; y en este caso nos interesa que las llamadas al método PintaCadenaO se realicen de
forma alternativa. Se necesita por tanto mantener una cierta coordinación entre los dos hilos, para
ello se usan los métodos waitQ, notifyQ y notifyAIIQ:
• Objeto.waitO: un hilo que llama al método waitO de un cierto objeto queda
suspendido hasta que otro hilo llame al método notifyQ o notifyAHO del IDismo
objeto.
100 Programación de servicios y procesos
• Objeto.notifyQ: despierta sólo a uno de los hilos que realizó una llamada a waitQ
sobre el mismo objeto notificándole de que ha habido un cambio de estado sobre el
objeto. Si varios hilos están esperando el objeto, solo uno de ellos es elegido para ser
despertado, la elección es arbitraria.
• Objeto.notifyAIIO: despierta todos los hilos que están esperando el objeto.
Los métodos notifyQ y waitQ pueden ser invocados sólo desde dentro de un método
sincronizado o dentro de un bloque o una sentencia sincronizada.
Por ejemplo, imaginemos una aplicación donde un hilo (el productor) escribe datos en un
fichero mientras que un segundo hilo (el consumidor) lee los datos del mismo fichero; en este
caso los hilos comparten un mismo recurso (el fichero) y deben sincronizarse para realizar su
tarea correctamente.
EJEMPLO PRODUCTOR-CONSUMIDOR
Se definen 3 clases, la clase Cola que será el objeto compartido entre el productor y el
consumidor; y las clases Productor y Consumidor. En el ejemplo el productor produce números
y los coloca en una cola, estos serán consumidos por el consumidor. El recurso a compartir es la
cola con los números.
El productor genera números de Oa 5 en un bucle for, y los pone en el objeto Cola mediante
el método putO; después se visualiza y se hace un pausa con sleepQ, durante este tiempo el hilo
esta en el estado Not Runnable (no ejecutable):
public class Productor extends Thread {
private Cola col a;
private int n;
La clase Consumidor es muy similar a la clase Productor, solo que en Jugar de poner un
número en el objeto Cola lo recoge llamando al método getO. En este caso no se ha puesto pausa,
con esto hacemos que el consumidor sea más rápido que el productor:
public class Consumidor extends Thread {
private Cola cola;
private int n;
La clase Cola define 2 atributos y dos métodos. En el atributo numero se guarda el número
entero y el atributo disponible se utiliza para indicar si hay disponible o no un número en la cola.
El método putO guarda un entero en el atributo numero y hace que este esté disponible en la cola
para que pueda ser consumido poniendo el valor true en disponible (cola llena). El método getO
devuelve el entero de la cola si está disponible (disponible=true) y antes pone la variable a fa/se
indicando cola vacía; si el número no está en la cola (disponible=false) devuelve -1;
public c lass Cola {
prívate int numero;
prívate boolean disponible false ; //inicialmente cola vacía
En el método mainO que usa las clases anteriores creamos 3 objetos, un objeto de la clase
Cola , un objeto de la clase Productor y otro objeto de la clase Consumidor. Al constructor de las
clases Productor y Consumidor le pasamos el objeto compartido de la clase Cola y un número
entero que lo identifique:
public class Produc_Consum {
public static void main(String [] a rgs) {
Cola cola= new Cola();
Productor p = new Productor (cola , 1) ;
Consumidor e= new Consumidor( cola , 1);
p . start ();
c . start () ;
O=>Productorl : produce : o
O=>Consumidorl : consume : o
l=>Productorl : p r oduce : 1
l =>Consumidorl : consume : 1
2=>Productor l : produce : 2
2=>Consumidorl : consume : 2
3=>Productorl : produce : 3
3=>Consumidorl : consume : 3
4=>Productorl : produce : 4
4=>Consumidorl : consume : 4
ACTIVIDAD 2.9
Prueba las clases Productor y Consumidor quitando el método sleepO del productor o
añadiendo uno al consumidor para hacer que uno sea más rápido que otro. ¿Se obtiene la salida
deseada?.
Para obtener la salida anterior es necesario que los hilos estén sincronizados. Primero hemos
de declarar los métodos getO y putO de la clase Cola como synchronized, de esta manera el
productor y consumidor no podrán acceder simultáneamente al objeto Cola compartido; es decir
el productor no puede cambiar el valor de la cola cuando el consumidor esté recogiendo su valor;
y el consumidor no puede recoger el valor cuando el productor lo esté cambiando:
public synchronized int ge t() {
// inst rucc i ones
Cola vacía
Cola llena
put() •• COLA
.. ·······
•••• •• wait()
Productor
disponible=false
notifyAII()
Consumidor
Figura 2.7. Método get() devuelve valor, put() espera.
El método putO tiene que esperar a que la cola se vacíe para poner el valor, entonces espera
(wait) mientras haya valor en la cola (while (disponible)). Cuando la cola se vacía, disponible es
false, entonces se sale del bucle, se asigna el valor a la cola, se vuelve a poner disponible a true
(porque la cola esta llena) y se notifica a todos los hilos que comparten el objeto este hecho:
La ejecución del ejemplo no siempre muestra la salida esperada, aunque las iteraciones sí
aparecen correctamente:
Para evitarlo tendremos que visualizar los mensajes de salida en la clase Cola, en lugar de
visualizar los mensajes en el productor y el consumidor. Haciendo los cambios se muestra la
siguiente salida:
Se produce: o
Se consume : o
Se produce: 1
Se consume : 1
Se produce : 2
Se consume : 2
Se produce: 3
Se consume: 3
Se produce: 4
Se consume: 4
ACTIVIDAD 2.1 O
Prueba el ejemplo anterior usando 2 consumidores y un productor. La salida debe ser parecida
a esta, en el productor se producen todas las iteraciones, en los consumidores no, ya que solo se
producen números en 5 iteraciones:
0=> Productor: 1, produce O
O=> Consumidor : 1 , consume O
O=> Consumi dor : 2 , consume 1
1=> Productor: 1, produce 1
1=> Consumidor : 1 , consume 2
2=> Productor: 1, produce 2
1=> Consumidor : 2, consume 3
3=> Pr oductor: 1 , produce 3
2=> Cons umidor : 1, consume 4
4=> Productor : 1 , produce 4
Fin productor ...
Modifica la clase Productor para que envie las cadenas PING y PONO (de forma alternativa,
una vez PINO y otra vez PONG) a la cola y la clase Consumidor tome la cadena de la cola y la
visualice. La salida tiene que mostrar lo siguiente: PINO PONG PINO PONO PING PONO
PING PONO PINO PONO PINO PONO PINO PONG PINO PONO PING PONO PING
PONO PINO PONO PING PONO PINO PONG PINO ....
106 Programación de servicios y procesos
COMPRUEBA TU APRENDIZAJE
1°) Crea una clase que extienda Thread cuya única funcionalidad sea visualizar el mensaje
"Hola mundo". Crea un programa Java que visualice el mensaje anterior 5 veces creando para
ello 5 hilos diferentes usando la clase creada anteriormente. Modifica el mensaje "Hola mundo"
en el hilo para incluir el identificador del hilo. Prueba de nuevo el programa Java creado
anteriormente.
2°) Crea una clase que implemente la interfaz Runnable cuya umca funcionalidad sea
visualizar el mensaje "Hola mundo" seguido de una cadena que se recibirá en el constructor (es
decir al crear un objeto de este tipo se enviará una cadena) y seguido del identificador del hilo.
Crea un programa Java que visualice el mensaje anterior 5 veces creando para ello 5 hilos
diferentes usando la clase creada anteriormente. Luego haz que antes de visualizar el mensaje el
hilo espere un tiempo proporcional a su identificador; usa para ello el método sleepQ. ¿Qué
diferenc ias observas al ejecutar el programa usando o no el método sleepO?
3°) Implemente un programa que reciba a través de sus argumentos una lista de ficheros de
texto y cuente el número de caracteres que hay en cada fichero. Modifica el programa para que se
cree un hilo por cada fichero a contar. Muestra lo que se tarda en contar cada fichero en la
primera tarea secuencial y usando hilos. Para calcular el tiempo que tarda en ejecutarse un
proceso podemos usar el método System.curretttTimeMillisO de la siguiente manera:
long t_comienzo, t_fin;
t_comienzo = System.currentTimeMillis();
Proceso(); //llamamos al proceso
t fin= System.currentTimeMillis();
long tiempototal = t_fin t _ comienzo;
System.out.println( " El proceso ha tardado: "+ tiempototal +" miliseg " );
4°) Haz un programa Java que reciba a través de sus argumentos una lista de ficheros de texto
y cuente el número de palabras que hay en cada fichero. Se debe crear un hilo por cada fichero a
contar. Muestra el número de palabras de cada fichero y lo que tarda en contar las palabras.
5°) Realiza un applet en el que una letra se mueva horizontalmente por la pantalla y rebote,
Figura 2.8. La letra debe empezar en las posiciones x = 1 e y = 100 y se moverá de izquierda a
derecha avanzando la x. Cuando la x sea mayor que el ancho del applet (getSizeO. width), hay que
hacer que rebote y se mueva de derecha a izquierda. Habrá que comprobar si la letra llega a la
posición 1 para hacerla rebotar de nuevo y se mueva de izquierda a derecha. El botón Finalizar
Hilo, detiene el movimiento de la letra y muestra Reanudar Hilo, al pulsarlo de nuevo la letra
continua su movimiento y el botón muestra el texto Finalizar Hilo.
Capítulo 2. Programación multihilo 1 07
lijvoso_ - o x [iijv-._ - o X
o o
6°) Realiza una pantalla gráfica para simular la carrera de 3 hilos. Inicialmente se debe
mostrar la pantalla de la Figura 2.9, donde se muestran 3 barras de progreso (componente
JProgressBar), 3 controles deslizantes (componente JS!ider) y 3 campos en los que se irá
mostrando el contador de cada hilo. En las barras de progreso se va mostrando como va la
carrera; inicialmente la prioridad asignada a cada hilo es de 5, se puede variar usando el control
deslizante. El botón Comenzar Proceso crea los hilos y lanza su ejecución teniendo en cuenta la
prioridad seleccionada, que se debe mostrar en la pantalla. El botón se desactivará volviendo a
activarse cuando la carrera finalice, pudiendo empezar de nuevo otra carrera. Cuando la carrera
finaliza se debe mostrar el hilo ganador. En el hilo se usará sleepQ, el constructor debe recibir un
valor numérico que indicará el tiempo que el hilo se queda dormido. Prueba el ejercicio con el
mismo valor para todos los hilos y con un valor aleatorio, por ejemplo: (long) Math.randomO *
1000 + l. Ejecuta varias veces el ejercicio y comprueba si el hilo de máxima prioridad es el que
gana.
0 X :ij USANDO PRIORIDAD€S. CAAA1RA DE . .os O X
=Q==
PnoOOad: 5 HilO 1 POOn:lad: 3
S 10
HIL02
===O==== Priondad: 5
S 10
HILO J HJLOJ
'"---=:;..;...:=--0
-
10
98 99 100
GANAHIL03
7°) Crea una clase para gestionar el saldo de una Cuenta. Debe tener métodos para obtener el
saldo actual, hacer un ingreso (se incrementa al saldo), hacer un reintegro (se le resta al saldo),
controlar si hay algún error por ejemplo si se hace un reintegro y no hay saldo; o si se hace un
ingreso y el saldo supera el máximo; mostrar mensajes con los movimientos que se realicen. La
cuenta recibe en su constructor el saldo actual y el valor máximo que puede tener. Los métodos
de ingreso y reintegro deben definirse como synchronized .
Crea después la clase Persona que extienda T hread y que realice en su método runO 2
ingresos y 2 reintegros alternándolos y con algún sleepO en medio. Para crear los movimientos
de dinero generar números aleatorios entre 1 y 500 con la función randomO: int aleatorio =
ffint) (Math.randomO*500+1) . El constructor de la clase debe llevar el nombre de la persona.
108 Programación de servicios y procesos
Crea en el método mainO un objeto Cuenta compartido por varios objetos Persona e inicia el
proceso de realizar movimientos en la cuenta.
8°) Partiendo de la Actividad 2_ 4, realiza la misma operación para suspender y reanudar el
hilo, pero ahora se usará una pantalla gráfica con varios botones y se lanzarán dos hilos, véase
Figura 2.1 O. El botón Comenzar Proceso crea los dos hilos y lanza su ejecución, los hilos sólo se
crean una vez, el botón se desactivará al pulsarle. Cada hilo tendrá su botón para suspenderlo o
reanudarlo. Se debe mostrar un mensaje en pantalla que indique el estado de cada hilo, corriendo
o suspendido. El botón Finalizar Proceso detiene los dos hilos y muestra en consola el valor
fmal de cada contador. El cierre de la ventana hace lo mismo. El constructor del hilo recibe dos
parámetros, uno con el nombre del hilo y el segundo la cantidad de milisegundos que permanece
el hilo dormido.
Comenzar Proceso
Reanudar
L Suspender su~-.J
23 28
9°) Realiza una pantalla gráfica para iniciar dos hilos y finalizar su ejecucwn usando
interrupciones. Se deben mostrar varios botones, véase Figura 2.11. El botón Comenzar Proceso
crea los dos hilos y lanza su ejecución, los hilos sólo se crean una vez, el botón se desactivará al
pulsarle. Cada hilo tendrá su botón para interrumpir su ejecución. Se debe mostrar un mensaje en
palltalla que indique si el hilo está corriendo o se ha sido interrumpida su ejecución. El botón
Finalizar Proceso detiene los dos hilos y muestra en consola el valor final de cada contador. El
cierre de la ventana hace lo mismo. El constructor del hilo recibe dos parámetros, uno con el
nombre del hilo y el segundo la cantidad de milisegundos que permanece el hilo dormido.
Wil EJEClTTAR EltmRRUMPIR HilOS 0 X Gj EJEClTTAR EINTERRUMPIR HILOS o X
Comet~Ur Proceso
20 2.1
11 °) Se trata de simular el juego para adivinar un número. Se crearán varios hilos, los hilos
son los jugadores que tienen que adivinar el número. Habrá un árbitro que generará el número a
adivinar, comprobará la jugada del jugador y averiguará a qué jugador le toca jugar. El número
tiene que estar comprendido entre 1 y 1O, usa la siguiente fórmula para generar el número: 1 +
(int) (JO* Math.randomO);
Se defmen 3 clases:
• Arbitro: Contiene el número a adivinar, el turno y muestra el resultado. Se defmen los
siguientes atributos: el número total de jugadores, el turno, el número a adivinar y si
el juego acabó o no. En el constructor se recibe el número de jugadores que participan
y se inicializan el número a adivinar y el turno. Tiene varios métodos: uno que
devuelve el turno, otro que indica si el juego se acabó o no y el tercer método que
comprueba la jugada del jugador y averigua a quien le toca a continuación, este
método recibirá el identificador de jugador y el número que ha jugado; deberá
definirse como synchronized, así cuando un jugador está haciendo la jugada, ningún
otro podrá interferir. En este método se indicará cual es el siguiente turno y si el juego
ha finalizado porque algún jugador ha acertado el número.
• Jugador: Extiende T hread. Su constructor recibe un identificador de jugador y el
árbitro, todos los hilos comparten el árbitro. El jugador dentro del método run
comprobará si es su turno, en ese caso generará un número aleatorio entre 1 y 1O y
creará la jugada usando el método correspondiente del árbitro. Este proceso se
repetirá hasta que el juego se acabe.
• Main: Esta clase inicializa el árbitro indicándole el número de jugadores y lanza los
hilos de los jugadores, asignando un identificador a cada hilo y enviándoles el objeto
árbitro que tienen que compartir.
Ejemplo de salida al ejecutar el programa:
NÚMERO A ADIVINAR : 3
Jugadorl dice: 9
Le toca a Jug2
Jugador2 dice: 9
Le toca a Jug3
Jugador3 dice: 10
Le toca a Jugl
Jugadorl dice: 4
Le toca a Jug2
Jugador2 dice: 7
Le toca a Jug3
Jugador3 dice : 7
Le toca a Jugl
Jugadorl dice: 6
Le toca a Jug2
Jugador2 dice : 3
Jugador 2 gana, adivinó el número!!!