0% encontró este documento útil (0 votos)
29 vistas43 páginas

Hilos en Java

Este documento aborda la programación multihilo en Java, explicando conceptos clave como la creación, gestión y sincronización de hilos. Se detallan las diferencias entre hilos y procesos, así como métodos útiles para trabajar con hilos en Java. Además, se presentan ejemplos prácticos de cómo implementar hilos y gestionar su ejecución en programas concurrentes.

Cargado por

zapatatome
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)
29 vistas43 páginas

Hilos en Java

Este documento aborda la programación multihilo en Java, explicando conceptos clave como la creación, gestión y sincronización de hilos. Se detallan las diferencias entre hilos y procesos, así como métodos útiles para trabajar con hilos en Java. Además, se presentan ejemplos prácticos de cómo implementar hilos y gestionar su ejecución en programas concurrentes.

Cargado por

zapatatome
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
Está en la página 1/ 43

PROGRAMACIÓN MULTIHILO

Contenidos Objetivos

Hilos. Estados de un hilo. Conocer las características


de los hilos en Java.
Gestión de hilos.
Crear y gestionar hilos.
Creación de hilos en Java.
Crear programas para
Compartir información entre compartir información
hilos. entre hilos.
Sincronización de hilos. Crear programas formados
Gestión de prioridades. por varios hilos
sincronizdos.

RESUMEN DEL CAPÍTULO


En este capítulo estudiaremos los hilos. Aprenderemos a crear y gestionar hilos en
programas Java.
68 Programación de servicíos y procesos

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.

2.2. QUÉ SON LOS HILOS


Un hilo (hebra, thread en inglés) es una secuencia de código en ejecución dentro del contexto
de un proceso. Los hilos no pueden ejecutarse ellos solos, necesitan la supervisión de un proceso
padre para ejecutarse. Dentro de cada proceso hay varios hilos ejecutándose. La Figura 2.1
muestra la relación entre hilos y procesos.
Proceso Proceso

Figura 2.1. Relación entre hilos y procesos.

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.

2.3. CLASES PARA LA CREACIÓN DE HILOS


En Java existen dos formas para crear hilos: extendiendo la clase T hread o implementando la
interfaz Run nable. Ambas son parte del paquete java.la ng.

2.3.1. La clase THREAD


La forma más simple de añadir funcionalidad de hilo a una clase es extender la clase T hread.
O lo que es lo mismo crear una subclase de la clase Thread. Esta subclase debe sobrescribir el
método r unO con las acciones que el hilo debe desarrollar. La clase Thread defme también los
métodos startQ y stopQ (actualmente en desuso) para iniciar y parar la ejecución del hilo. La
forma general de declarar un hilo extendiendo T hread es la siguiente:
class NombreHilo e x tends Thread {
// propieda d e s , c o nstructo r e s y mé tod o s d e l a c l ase
public void run() {
Capitulo 2. Programación multihilo 69

// acciones que lleva a cabo el hilo

Para crear un objeto hilo con el comportamiento de NombreHilo escribo:


NombreHilo h = new NombreHilo();

Y para iniciar su ejecución utilizamos el método startO:


h . start () ;
El siguiente ejemplo declara la clase PrimerHilo que extiende la clase Thread, desde el
constructor se inkializa una variable numérica que se usará para pintar un número de veces un
mensaje; en el método runO se escribe la funcionalidad del hilo:
public class PrimerHilo extends Thread {
private int x;
PrimerHilo (int x)
{
this .x=x;

public void run() {


for (int i=O; i<x ; i++)
System. out.println( "En el Hilo ... "+i );

}//PrimerHilo

A continuación, para crear un objeto hilo escribimos:


PrimerHilo p = new PrimerHi l o (10);
Y para iniciar su ejecución:
p . start ();
Dentro de la clase anterior podemos añadir el método mainO para crear el hilo e iniciar su
ejecución:
public static void main(String[] args) {
PrimerHilo p = new Prime rHilo(lO) ;
p . s t art ();
}// main

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 () ;

System .out . p r intln( " 3 HILOS INICIADOS . . . " );


}// main

}// 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

public void run() {


for (int i =O; i<S ; i++)
System . out.println( " Hilo: " + getName() + " C " + i) ;

}// HiloEjemplol_V2

Y la clase que usa el hilo UsaHiloEjemplol_V2java:


public class UsaHiloEjemplol_V2
public static void main(String[) args)
HiloEjemplol hl new HiloEjemplol ( " Hilo 1");
HiloEjemplol h2 new HiloEjemplol ( " Hilo 2");
HiloEjemplol h3 new HiloEjemplol ("Hilo 3 ");

hl. start () ;
h2. start () ;
h3. start ();

System.out.println( " 3 HILOS INICIADOS ... " );


}
}//UsaHiloEjemplol_V2

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

int getPriorityO Devuelve la prioridad del hilo.

setPriority(int p) Cambia la prioridad del hilo al valor entero p.

void interruptO Interrumpe la ejecución del hilo

boolean interruptedO Comprueba si el hilo actual ha sido interrumpido.

Thread Devuelve una referencia al objeto hilo que se está ejecutando


currentTbreadO actualmente.
Comprueba si el hilo es un hilo Daemon. Los hilos daemon o
demonio son hilos con prioridad baja que nonnalmente se ejecutan en
boolean isDaemonO segundo plano. Un ejemplo de hilo demonio que está ejecutándose
continuamente es el recolector de basura (garbage collector).
Establece este hilo como hilo Daemon, asignando el valor true, o
setDaemon(boolean on) como hilo de usuario, pasando el valor fa/se.

void stopO Detiene el hilo. Este método está en desuso.


Thread Devuelve una referencia al objeto hilo actualmente en ejecución.
currentThreadO
Este método devuelve el número de hilos activos en el grupo de hilos
int activeCountO del hiJo actual.
Devuelve el estado del hilo: NEW, RUNNABLE, BLOCKED,
Tbread.State getStateO WAITING, TIMED_ WAITING, TERMINA TED

En la URL https://docs.oracle.com/ jamse/ JO/docs!api! java/lang/Fhread.html se puede


consultar más información sobre todos estos métodos.
El siguiente ejemplo muestra el uso de algunos de los métodos anteriores:
public class HiloEjemplo2 extends Thread {
public void run() {
System . out .println(
"Dentro del Hi lo " + Thread.current Thread() .getName() +
" \n\tPrioridad " + Thread.currentThread() . getPriority() +
" \n\tiD " + Thread . currentThread() . getid() +
" \n\tHilos activos : " + Thread . currentThread () . activeCount ());
}
11
public static void main(String[] args) {
Thread .currentThread() .setName("Principal") ;//nombre a main
System . out . println(Thread . currentThread() . getName() );
System . out.println(Thread .currentThread() .toString() );

HiloEjemplo2 h = null ;

for ( int i = O; i < 3 ; i ++) {


h = new HiloEjemplo2() ; //crear hilo
h.setName("HILO" +i) ; //damos nombre al hilo
h .setPriority(i+l) ; //damos prioridad
h .start() ; //iniciar hilo
System . out .print l n(
" Información del " + h. getName () + " · " + h . toString () );
Capítulo 2. Programación multihilo 73

System. out . p rintln (" 3 HILOS CREADOS . . . ");


System. out .println (" Hi los a ctivos: " + Th read.act i v e Count());

}//

}// HiloE jemplo2

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

public static void main(String[] args) {


Thread.currentThread() .setName("Principal");
System.out. println(Thread . currentThread() .getName());
System.out.println(Thread.currentThread() . toStr ing());

ThreadGroup grupo= new ThreadGroup( "Grupo de hilos " ) ;


HiloEjemplo2Grupos h = new HiloEjemplo2Grupos();

Thread hl new Thread(grupo, h, "Hilo 1");


Thread h2 new Thread(grupo, h, "Hi lo 2 ");
Thread h3 new Thread(grupo, h, "Hilo 3");

hl. start () ;
h2. start () ;
h3. start ();

System.out.println( " 3 HILOS CREADOS ... ");


System.out.println("Hilos activos: "+ Thread .activeCount());
}
}// HiloEjemplo2Grupos

La ejecución muestra la siguiente salida:


Principal
Thread[Principal,S,main)
3 HILOS CREADOS ...
Hilos activos: 4
Informacion del hilo: Thread[Hilo l,S , Grupo de hilos]
Informacion del hilo : Thread(Hilo 2,5,Grupo de hilos]
Hilo 1 Finalizando la ejecución .
Hilo 2 Finalizando la ejecución.
Informacion del hilo: Thread [Hi lo 3,5,Grupo de hilos]
Hilo 3 Finalizando la ejecución.

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.

2.3.2. La interfaz RUNNABLE


Para añadir la funcionalidad de hilo a una clase que deriva de otra clase (por ejemplo, un
applet), siendo esta distinta de T hread, se utiliza la interfaz Runnable. Esta interfaz añade la
funcionalidad de hilo a una clase con solo implementarla. Por ejemplo, para añadir la
funcionalidad de hilo a un applet definimos la clase como:
public class Reloj extends Applet implements Runnable {}
La interfaz Runnable proporciona un único método, el método runO. Este es ejecutado por el
objeto hilo asociado. La forma general de declarar un hilo implementando la interfaz Runnable
es la siguiente:
Capítulo 2. Programación multihilo 75

class NombreHilo ~lements Runnable


//propiedades , constructores y métodos de la clase
public void run() {
//acciones que lleva a cabo el hilo

}//

Para crear un objeto hilo con el comportamiento de NombreHilo escribo lo siguiente:


NombreHilo h = new NombreHilo();
Y para iniciar su ejecución utilizamos el método startQ:
new Thread(h) . start();
O bien para lanzar el hilo escribimos lo anterior en dos pasos:
Thread hl = new Thread(h) .
hl . start()·;
O en un paso todo:
new Thread(new NombreHilo()) .s tart();
El siguiente ejemplo declara la clase PrimerHiloR que implementa la interfaz Runnable, en
el método runO se indica la funcionalidad del hilo, en este caso es pintar un mensaje y visualizar
el identificador del hilo actualmente en ejecución:
public class PrimerHiloR implements Runnable {
public void run() {
System . out.println( " Hola desde el Hilo! " +
Thread . currentThread() .getid());

}//PrimerHiloR

A continuación, se muestra la clase UsaPrimerHiloRjava donde se lanzan varios hilos del


tipo anterior de distintas formas:
public class UsaPr~erHiloR {
public static void main(String[] args) {
//Primer hilo
PrimerHiloR hilol = new Pr imerHiloR();
new Thread(hilol) .start() ;

//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.

Figura 2.2. Applet Reloj.java.

Hemos de tener en cuenta que al utiJjzar applet en versiones de Java 10 y superiores se


muestra una advertencia indicando que la API de applet y AppletViewer están en desuso.
En un applet se defmen varios métodos:
• init(): con instrucciones para inicializar el applet, este método es llamado una vez
cuando se carga el applet.
• start(): parecido a init() pero con la diferencia de que es llamado cuando se reinicia el
applet.
• paint(): que se encarga de mostrar el contenido del applet; se ejecuta cada vez que se
tenga que redibujar.
• stop(): es invocado al ocultar el applet, se utiliza para detener hilos.
El navegador web llama primero al método init(), luego a paint() y a continuación al método
startQ. El hilo lo crearemos dentro del método start() usamos la siguiente expresión:
hilo= new Thread(this);
Al especificar this en la sentencia new Thread() se indica que el applet proporciona el cuerpo
del hilo.
La estructura general de un applet que implementa Runnable es la siguiente:
import java . awt.*;
import java.applet.*;
public class AppletThread extends Applet implements Runnable {
private Thread hilo nul l;
public void init() {
}
public void start() {
if (hilo == null}
11 crea el hilo
hilo= new Thread(this};
hilo.start(}; //lanza el hilo

public void run() {


Thread hiloActual Thread.currentThread(};
Capítulo 2. Programación multihilo 77

whi l e {hi lo == h iloActual )


11 tare a repeti t i va
}

public void stop()


h i lo = n ull;

publ i c v oid paint(Graphics g) {


}

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
}

Donde se comprueba cual es el hilo actual con la expresión Thread. currentThreadO; el


proceso continúa o no dependiendo del valor de la variable del hilo; si la variable apunta al
mismo hilo que está actualmente en ejecución el proceso continúa; si es null el proceso fmaliza y
si la variable hace referencia a otro hilo es que ha ocurrido una extraña situación, la tarea
repetitiva no se ejecutará. Aplicamos la estructura anterior a nuestro applet, Relojjava, que
muestra la hora:
import java.ap p let .*;
imp ort java.awt . * ;
imp ort java . text . SimpleDateForma t ;
import java. util . * ;

public class Reloj extends Applet implements Runnable {


prívate Thread hi lo = nul l; //hilo
prívate Font fu e nte ; //tip o de letra para la hora
príva t e String horaActual ,
1111 ..

public void init() {


f uente= new Font( " Verdana " , Font. BOLD , 2 6 ) ;
setBackgr ound(Color . ye llow) ; //color de fondo
s etFont{ f u e nte) ; // fue nte

public void start() {


if (hilo == null )
hilo= new Th read(this) ;
hilo . start () ;

public void run() {


Thread hiloActual = Th r e ad . curre ntThread{) ;
while {hilo == hiloAct ual) {
SimpleDat e Format sdf = new SimpleDateForma t{ " HH : mm : ss " ) ;
Calendar cal = Ca lendar . get instance() ;
horaActual = sdf .format( ca l.get Time ());
r epaint() ; //actual izar conten i do del applet
78 Programación de servicios y procesos

try {
Thread.sleep (1 000 );
} catch (Inte r r up t edExc ept i o n e) {}

public void p aint(Graphics g) {


g . clearRect( l, 1 , getS ize() .width , getSize() . height) ;
g . drawStr ing (horaActua l , 20 , 50) ; //muestra la hora

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

Figura 2.3. Applet ContadorApplet.java.


Capítulo 2. Programación multihilo 79

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() ;

el s e if(e . getSource() == b2) //Pulso Pa rar contador


parar = true; //para que f i nalice el while en e l método run

}//actíonPerformed

En el método runO se escribe la funcionalidad del hilo. Se irá incrementando el contador


siempre y cuando la variable parar sea false. El código completo es el siguiente:
import java .applet. Applet ;
import j a va .awt . * ;
:mport j a va .awt . event . * ;

public class ContadorApplet extends Applet


~plements Runnable, ActionListener
p r ivate Thread h ;
long CONTADOR = O;
p rí vate boo lean p a rar ;
p r ívate Font fuente ;
p r ívate Button bl , b2 ; //bot ones de l App let

public void start() {}

public void init() {


setBackground(Color . yellow) ; //color de fondo
add (b l =new Button( "Inící ar contador " ) ) ;
bl. addActionLíst ener(thís) ;
add(b2=new Button( " Pa rar contador " )) ;
b2 . addAct ionLíst e ne r( thís );
fuente= new Font( " Verdana" , Font . BOLD, 26);//típo letra
80 Programación de servicios y procesos

public void run() {


parar = f al se ;
Thread hi loActual = Th read. currentThread() ;
while (h == hiloActual && !parar) {
try {
Thread.sleep(300);
catch (InterruptedException e) {
e.printStackTrace();

repaint() ;
CONTADOR++;

public void paint(Graphics g) {


g.clearRect(O, O, 40 0 , 400) ;
g . setFont(fuente) ; //fuente
g .drawString(Long .toString((long)CONTADOR) , 80 , 100) ;

//para controlar que se pulsan los botones


publi c void actionPerformed(ActionEvent e)
if(e .getSource() == bl) //Pulso Ini ciar contador o Continuar
{
bl.setLabe l( "Continuar" ) ;
i f (h ! = null && h. isAlive ()) {} 1 /Si el hilo está corriendo
//no hago nada .
else {
//creo hilo la primera vez y cuando finaliza el método run
h = new Thread(th is);
h . start ();
}
} else if(e.getSource() == b2) //Pul s o Parar contador
parar=true; //para que finalice el while en el método run

}//actionPerformed

public void stop()


h = null;
}
}//fin ContadorApplet

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.

Figura 2.4. Applet Actividad 2.3.

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.

2.4. ESTADOS DE UN HILO


Un hilo puede estar en uno de estos estados:
• New (Nuevo): es el estado cuando se crea un objeto hilo con el operador new, por
ejemplo new HiloO , en este estado el hilo aún no se ejecuta; es decir, el programa no
ha comenzado la ejecución del código del método runO del hilo.
• Runnable (Ejecutable): cuando se invoca al método startO, el hilo pasa a este
estado. El sistema operativo tiene que asignar tiempo de CPU al hilo para que se
ejecute; por tanto, en este estado el hilo puede estar o no en ejecución.
• Dead (Muerto): un hilo muere por varias razones: de muerte natural, porque el
método runO fmaliza con normalidad; y repentinamente debido a alguna excepción
no capturada en el método runO. En particular, es posible matar a un hilo invocando
su método stopQ. Este método lanza una excepción ThreadDeath que mata al hilo.
Sin embargo, el método stopO está en desuso y no se debe llamar ya que cuando un
hilo se detiene, inmediatamente no libera los bloqueos de los objetos que ha
bloqueado. Esto puede dejar a los objetos en un estado inconsistente. Para detener un
hilo de manera segura, se puede usar una variable; en este ejemplo se usa la variable
stopHilo que se inicializa con un valor true y se utiliza dentro de un bucle en el
método runO. Para que termine el bucle del método runO se invoca al método
pararHiloO que cambia el valor de la variable afalse:
82 Programación de servicios y procesos

public class HiloEjemploDead extends Thread {


private boolean stopHilo= false;

public void pararHilo()


stopHilo = true;

public void run() {


while (!stopHilo)
System . out .print ln("En el Hilo " );

public static void main(String[] args) {


HiloEjemploDead h = new Hi loEj emplo Dead () ;
h . start();
for(int i =O ;i< lOOO OOO; i ++) ;//no hago nada
h.pararHilo() ; //parar el hilo

}//

}//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.

Del estado bloqueado se pasa a ejecutable cuando: expira el número de milisegundos de


sleepQ, se completa la operación de E/S, recibe los mensajes notifyO o notifyAllO, el bloqueo
del objeto fmaliza, o se llama al método resumeO. La Figura 2.5 muestra los estados que puede
tener un hilo y las posibles transiciones de un estado a otro. Los métodos resume(), suspendO y
stopO están en desuso.
Cuando un hilo está bloqueado (o, por supuesto, cuando muere), otro hilo está previsto para
funcionar. Cuando un hilo bloqueado se reactiva (por ejemplo, porque finaliza el número de
milisegundos que permanece dormido o porque la E/S que se esperaba se ha completado), el
planificador comprueba si tiene una prioridad más alta que el hi lo que se está ejecutando
actualmente. Si es así, se antepone al actual hilo y selecciona el nuevo hilo para ejecutarlo. En
una máquina con varios procesadores, cada procesador puede ejecutar un hilo, se pueden tener
varios hilos en paralelo. En tal máquina, un hilo con máxima prioridad se adelantará a otro si no
hay disponible procesador para ejecutarlo.
Capítulo 2. Programación multihilo 83

Figura 2.5. Estados de un hilo1 .

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.

2.5. GESTIÓN DE HILOS


En ejemplos anteriores hemos visto como crear y utilizar los hilos, vamos a dedicar un
apartado a los pasos vistos anteriormente.

1
Figura obtenida del libro Core Java™ 2: Volume II- Advanced Features. Cay S. Horstmann, Gary Comell.
84 Programación de servicios y procesos

2.5.1 . CREAR Y ARRANCAR HILOS


Para crear un hilo extendemos la clase Thread o implementamos la interfaz Runnable. La
siguiente linea de código crea un hilo donde MiHilo es una subclase de Thread (o una clase que
implementa la interfaz Runnable), se le pasan dos argumentos que se deben definir en el
constructor de la clase y se utilizan, por ejemplo, para iniciar variables del hilo:
MiHilo h = new MiHilo( "Hilo 1 " , 200 ) ;
Si todo va bien en la creación del hilo tendremos en h el objeto hjJo. Para arrancar el hilo
usamos el método startO de esta manera si extiende Thread:
h. s tart ();
Y si implementa Runnable lo arrancamos así:
new Th read(h) . start() ;
Lo que hace este método es llamar al método runO del hilo que es donde se colocan las
acciones que queremos que haga el hilo, cuando fmalice el método finalizará también el hilo.

2.5.2. SUSPENSIÓN DE UN HILO


En ejemplos anteriores usamos el método sleepO para detener un hilo un número de
milisegundos. Realmente el hilo no se detiene, sino que se queda "dormido" el número de
milisegundos que indiquemos. Lo utilizábamos en los ejercicios de los contadores para que nos
diese tiempo a ver cómo se van incrementando de 1 en l.
El método suspendO permite detener la actividad del hilo durante un intervalo de tiempo
indeterminado. Este método es útil cuando se realizan applets con animaciones y en algún
momento se decide parar la animación para luego continuar cuando lo decida el usuario. Para
volver a activar el hilo se necesita invocar al método resumeO.
El método suspendO es un método obsoleto y tiende a no utilizarse porque puede producir
situaciones de interbloqueos. Por ejemplo, si un hilo está bloqueando un recurso y este hilo se
suspende puede dar lugar a que otros hilos que esperaban el recurso queden "congelados" ya que
el hilo suspendido mantiene los recursos bloqueados. Igualmente el método resumeO también
está en desuso.
Para suspender de forma segura el hilo se debe introducir en el hilo una variable, por ejemplo
suspender y comprobar su valor dentro del método runo, es lo que se hace en la llamada al
método suspender.esperandoParaReanudarO del ejemplo siguiente. El método SuspendeO del
hilo da valor true a la variable para suspender el hilo. El método Reanuda O da el valor fa/se para
que detenga la suspensión y continue ejecutándose el hilo:
class MyHilo extends Thread {
private SolicitaSuspender suspender = new SolicitaSuspender() ;
//pet ición de SUSPENDER HI LO
public void Suspende() { suspender . set (trua) ; }
//petición de CONTINUAR
public void Reanuda() { suspender . set(false) ; }

public void run() {


t ry
while(haya trabajo por hace r ) {

suspender.esperandoParaReanudar() ; //comprobar
Capitulo 2. Programación multihilo 85

} catch (InterruptedException excepti on) { }


}
} //

Para mayor claridad, se envuelve la variable (a la que se hacía alusión anteriormente) en la


clase SolicitaSuspender, en esta clase se define el método setO que da el valor true o false a la
variable y llama al método notifyAliQ, este notifica a todos los hilos que esperan (han ejecutado
un waitQ) un cambio de estado sobre el objeto. En el método esperandoParaReanudarO se hace
un waitO cuando el valor de la variable es true, el método waitO hace que el hilo espere hasta
que le llegue un notifyQ o un notifyAIIO;
public class SolicitaSuspender {
prívate boolean suspender;

public synchronized void set(boolean b) {


suspender= b ; //cambio de estado sobre el objeto
notifyAll () ;

public synchronized void esperand oParaReanudar ()


throws InterruptedExcept ion
while (suspende r )
wait(); //SUSPENDER HILO HASTA RECIBIR notify() o notifiAll()
}
}//

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.

2.5.3. PARADA DE UN HILO


El método stopO detiene la ejecución de un hilo de forma permanente y ésta no se puede
reanudar con el método startQ:
h . stop();
86 Programación de servicios y procesos

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 " );

Syste m.out.println( " FIN HILO");

public void interrumpir ()


i n terru pt () ;

public static void main( String [] args) {


HiloEjemplointerrup h = new HiloEjemplointerrup();
h . start () ;
for(int i=O ; i<lOOOOOOOOO ; i++) ; //no hago nada
h . interrumpir();
}//
}//

Un ejemplo de ejecución muestra la siguiente información:


En el Hilo
En el Hilo
HA OCURRIDO UNA EXCEPCIÓN
FIN HILO

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 ;

public void run() {


for {int i=l ; i<= n ; i++)
System . out . println(getName{) + ": "+ i ) ;
try {
sleep {1000) ;
} catch {InterruptedExce ption ignore) {}

S ystem . out .pri nt ln {" Fin Bucle " +getName{));


}
}//

public class EjemploJoin {


public static void main(String[] args)
Hi loJoin hl new HiloJoi n{ " Hi lo1 ", 2) ;
Hi l oJoin h2 n ew Hi loJoin{ " Hilo2 ", 5 );
HiloJoin h 3 new HiloJoin{"Hilo3 ", 7 );
h l. s tart {) ;
h 2. start {) ;
h3 . s tart {) ;
try {
h l . join() ; h 2 . join() ; h3 . join() ;
} catch ( Inter ruptedException e) { }
System. out . p rintln(" FINAL DE PROGRAMA " );
}
}//
En el método mainO se crean 3 hilos, cada uno da un valor diferente a la n , el primero el valor
más pequeño y el tercero el valor más grande, parece lógico que por los valores del contador el
primer hilo debe terminar el primero y el tercer hilo el último. Llamando a joinO podemos hacer
que mainO espere a la finalización de los hilos y cada hilo finalice en el orden marcado según la
llamada a joinQ, cuando salgan del bloque try-catch los tres hilos habrán fmalizado y el texto
FINAL DE PROGRAMA se visualizará al fmal.
La ejecución muestra la siguiente salida:
Hilol : 1
Hi lo3 : 1
Hilo 2 : 1
Hilo2: 2
Hilo3 : 2
Hilo1 : 2
Hilo2: 3
Hilo3: 3
Fin Bucle Hilol
Hi lo2 : 4
Hilo 3: 4
Hilo2: 5
Hilo3 : 5
88 Programación de servicios y procesos

Fin Bucle Hilo2


Hilo3: 6
Hilo3: 7
Fin Bucle Hilo3
FINAL DE PROGRAMA

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.

2.6. GESTIÓN DE PRIORIDADES


En el lenguaje de programación Java, cada hilo tiene una prioridad. Por defecto, un hilo
hereda la prioridad del hilo padre que le crea, esta se puede aumentar o disminuir mediante el
método setPriorityQ. El método getPriorityQ devuelve la prioridad del hilo.
La prioridad no es más que un valor entero entre 1 y 1O, siendo el valor 1 la mínima prioridad,
MIN_PRIORITY; y el valor 10 la máxima, MAX_PRIORITY. NORM_PRIORITY se define
como 5. El planificador elige el hilo que debe ejecutarse en función de la prioridad asignada; se
ejecutará primero el hilo de mayor prioridad. Si dos o más hilos están listos para ejecutarse y
tienen la misma prioridad, la máquina virtual va cediendo control de forma cíclica (round-robin).
El planificador es la parte de la máquina virtual de Java que decide qué hilo ejecutar en cada
momento. Da más ventaja a hilos con mayor prioridad; hilos de igual prioridad en algún
momento se ejecutarán.
El hilo de mayor prioridad sigue funcionando hasta que:
• Cede el control llamando al método yieldQ para que otros hilos de igual prioridad se
ejecuten.
• Deja de ser ejecutable (ya sea por muerte o por entrar en el estado de bloqueo).
• Un hjlo de mayor prioridad se convierte en ejecutable (porque se encontraba dormjdo
o su operación de E/S ha fmalizado o alguien lo desbloquea llamando a los métodos
notifyAIIQ 1 notifyO).
El uso del método yieldQ devuelve automáticamente el control al planificador, el hilo pasa a
un estado de listo para ejecutar. Sin éste método el mecanismo de multihilos sigue funcionando
aunque algo más lentamente.
En el siguiente ejemplo se crea una clase que extiende T hread, se define una variable
contador que será incrementada en el método runO, se defme un método para obtener el valor de
la variable y otro método para finalizar el hilo, en el constructor se le da nombre al hilo:
c lass Bilo Prio ridadl extends Thread {
prívate int e = O;
private boolean stopHilo = false ;
Capítulo 2. Programación multihilo 89

public HiloPrioridadl(String nombre) {


super(nombre);

public int getContador()


return e;

public void pararHilo ()


stopHilo = true;

publi c voi d run () {


while {!stopHilo)
try {
Thread.sleep{2);
catch (Exception e) { }
e++;

Sys tem . out.println( " Fin hilo "+this.getName()) ;


}//
}//HiloPrioridadl

A continuación se crea la clase EjemploHiloPrioridadl con el método mainO en el que se


definen 3 objetos de la clase HiloPrioridadl, a cada uno se le asigna una prioridad. El contador
del hilo al que se le ha asignado mayor prioridad contará más deprisa que el de menos prioridad.
Al finalizar cada hilo se muestran los valores del contador invocando a cada método
getContadorO del hjlo:
publi c class EjemploHilo Pri oridadl {
public static voi d ma in(String args[])
HiloPrioridadl hl new Hi l oPrioridadl("Hilol");
HiloPrioridadl h2 new HiloPrioridadl("Hilo2");
HiloPrioridadl h3 new HiloPrioridadl("Hilo3");

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() ;

System .out.println( " h2 (Prioridad Maxima): "+ h2.getContador());


System .out.println( " hl (Prioridad Normal): " + hl.getContador());
System .out.println( "h3 (Pr i oridad Mínima): " + h3 . getContador());
}
11 EjemploHiloPrioridadl
90 Programación de servicios y procesos

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:

h2 (Prioridad Maxima) : 4856


Fin hilo Hilo3
Fin hilo Hilo1
Fin hilo Hi lo2
h 1 (Prioridad Normal) : 4855
h3 (Prioridad Minima) : 4854

h2 (Prioridad Maxima): 4767


h1 (Prioridad Normal) : 4684
h3 (Prioridad Minima) : 4668
Fin hilo Hilo2
Fin hilo Hilo1
Fin hilo Hi lo 3

h2 (Prioridad Maxima) : 4565


h1 (Prioridad Normal): 4545
h3 (Prioridad Minima) : 4523
Fi n hilo Hilo2
Fin hilo Hilol
Fin hilo Hilo3

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

h2 (Prioridad Maxima) : 4518


Fin hilo Hilo2
h1 (Prioridad Normal): 4518
h3 (Prioridad Minima): 4517
Fin hilo Hi lo 1
Fin hilo Hilo3

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) ;

public void run() {


System.out. println( "E jecutando [" + getName() + " ] ");
for (int i = 1 ; i <= 5; i++)
System.out.println("\t("+getName()+": " + i+")");
Capftufo 20 Programación multihilo 91

public static void main(String[] args) {


EjemploHiloPrioridad2 h1 new EjemploHiloPrioridad2( " Uno " ) ;
EjemploHiloPrioridad2 h2 new EjemploHiloPrioridad2(''Dos" ) ;
EjemploHiloPrioridad2 h3 new EjemploHiloPrioridad2("Tres");
EjemploHiloPrioridad2 h4 new EjemploHiloPrioridad2("Cuatro" ) ;
EjemploHiloPrioridad2 h5 new EjemploHiloPrioridad2( "Cinco " );

//asignamos prioridad
h1 osetPriority(Thread . MIN_PRIORITY) ;
h2 . setPriority(3) ;
h3 . setPriority(Thread . NORM_PRIORITY ) ;
h4 . setPriority(7) ;
h5osetPriority(Thread.MAX_ PRIORITY);

//se ejecutan los hilos


hl. start ();
h2 ostart () ;
h3 ostart () ;
h4. start () ;
h5 . start () ;

Un ejemplo de ejecución muestra la siguiente dalida:


Ejecutando [Cuatro]
(Cuatro : 1)
(Cuatro : 2)
(Cuatro: 3)
(Cuatro : 4)
(Cuatro : 5)
Ejecutando [Tres]
(Tres : 1)
(Tres : 2)
(Tres : 3)
(Tres : 4)
(Tres: 5)
Ejecutando [Cinco ]
(Cinco : 1)
(Cinco: 2 )
(Cinco : 3)
(Cinco: 4)
(Cinco: 5)
Ejecutando [Dos]
(Dos : 1)
(Dos : 2)
(Dos : 3)
(Dos: 4)
(Dos : 5)
=:jecutando [Uno)
(Uno : 1)
(Uno: 2)
(Uno : 3)
(Uno: 4)
(Uno : 5)
92 Programación de servicios y procesos

A la hora de programar hilos con prioridades hemos de tener en cuenta que el


comportamiento no está garantizado y dependerá de la plataforma en la que se ejecuten los
programas y de las aplicaciones que se jecuten al mismo tiempo. En la práctica casi nunca hay
que establecer a mano las prioridades.
Cuando un hilo entra en ejecución y no cede voluntariamente el control para que puedan
ejecutarse otros hilos, se dice que es un "hilo egoísta". Algunos sistemas operativos, como
Windows, combaten estas situaciones con una estrategia de planificación por división de tiempos
(time-slicing o tiempo compartido), que opera con hilos de igual prioridad que compiten por la
CPU. En estas condiciones el sistema operativo divide el tiempo de proceso de la CPU en
espacios de tiempo y asigna el tiempo de proceso a los hilos dependiendo de su prioridad. Así se
impide que uno de ellos se apropie del sistema durante un intervalo de tiempo prolongado.

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

2.7. COMUNICACIÓN Y SINCRONIZACIÓN DE HILOS


A menudo los hilos necesitan comunicarse unos con otros, la forma de comunicarse consiste
usualmente en compartir un objeto. En el siguiente ejemplo dos hilos comparten un objeto de la
clase Contador. Esta clase defme un atributo contador y tres métodos, uno de ellos incrementa
una unidad su valor, el otro lo decrementa y el tercero devuelve su valor; el constructor asigna un
valor inicial al contador:
class Contador {
private int e= O; //atributo contador
Contador(int e) { this . c e; }
public void incrementa() { e = e + 1;
public void decrementa() { e = e - 1;
public int valor() { return e; }
}// CONTADOR

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 ;

public void run()


for (int j = O; j < 300 ; j++) {
contador. incrementa(); 1 / incrementa el contador
try {
sleep(100) ;
} catch (InterruptedException e) {}
Capítulo 2. Programación multihilo 93

System . out. println (getName() + " contad or vale " +


contador. v a lor( ) ) ;
)
}// FIN HILOA

class HiloB extends Thread {


private Contador contador ;
public HiloB(String n , Contador e) {
s etNa me(n);
contador = e;

pub lic void run()


f o r (int j = O; j < 300 ; j++) {
contador.decrementa(); //decrementa el contador
try {
sl e ep( l 00) ;
} catch (Inte rrupte dException e) {}

System . out. pri ntln(getName() +"contador vale " +


con tador. v alor ( ) );
}
} // FIN HILOB

A continuación se crea el método mainO, donde primero se defme un objeto de la clase


Contador y se le asigna el valor inicial de lOO. A continuación, se crean los dos hilos pasándoles
dos parámetros: un nombre y el objeto Contador. Seguidamente se inicia la ejecución de los
hilos:
public class Compartirinfl {
public static void main(String[] args) {
Contado r cont = new Con tador (1 00) ;
HiloA a= new HiloA( " HiloA", cont) ;
Hi loB b = new Hi loB ("Hi loB", c ont);
a . start () ;
b. sta rt ();

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

Al probarlo sin el método sleepQ da la sensación de que la salida es la esperada, pero no


siempre nos va a dar dicha salida:
~iloA contador v a le 400
EiloB contador v a le 100

EiloB con tado r v a le 100


=:iloA contador vale 100

::iloA contador vale 43


::1108 contador v a le 43
94 Programación de servicios y procesos

HiloB contador vale 100


HiloA contador vale 400

HiloB contador vale -200


HiloA contador vale 100

2.7.1. BLOQUES SINCRONIZADOS


Una forma de evitar que esto suceda es hacer que las operaciones de incremento y decremento
del objeto contador se hagan de forma atómica, es decir, si estamos realizando la suma nos
aseguramos que nadie realice la resta hasta que no terminemos la suma. Esto se puede lograr
añadiendo la palabra synchronized a la parte de código que queramos que se ejecute de forma
atómica. Java utiliza los bloques synchronized para implementar las regiones críticas (que se
tratarón en el Capítulo 1). El formato es el siguiente:
synchronized (object) {
//sentencias críticas

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();

System . out . println(getNa me( ) + " contador vale "


+ contador.valor());

Para el HiloA:
public void run ()
synchronized (contador) {
for (int j = O; j < 300 ; j++)
contador.incrementa();

System.out.println(getName() + " contador vale "


+ contador.valor());

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

HiloA contador vale 400


HiloS contador vale 100

Si se cambia el orden de la ejecución de los hilos, primero el HiloB y luego el HiloA, la


salida será:
HiloS contador vale -200
HiloA contador vale 100

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.

2.7.2. MÉTODOS SINCRONIZADOS


Se debe evitar la sincronización de bloques de código y sustituirlas siempre que sea posible
por la sincronización de métodos, exclusión mutua de los procesos respecto a la variable
compartida. Imaginemos la situación que dos personas comparten una cuenta y pueden sacar
dinero de ella en cualquier momento; antes de retirar dinero se comprueba siempre si existe
saldo. La cuenta tiene 50€, una de las personas quiere retirar 40 y la otra 30. La primera llega al
cajero, revisa el saldo, comprueba que hay dinero y se prepara para retirar el dinero, pero antes
de retirarlo llega la otra persona a otro cajero, comprueba el saldo que todavía muestra los 50€ y
también se dispone a retirar el dinero. Las dos personas retiran el dinero, pero entonces el saldo
actual será ahora de -20.
Para sincronizar un método, simplemente añadimos la palabra clave synchronized a su
declaración. Por ejemplo, la clase Contador con métodos sincronizados seria así:
public class ContadorSincronizado
private int e = O;

public synchronized void incrementa() {


e ++ ;

publ ic synch ronized void decrementa() {


e--;

public synchronized int valor() {


return e;

El uso de métodos sincronizados implica que no es posible invocar dos métodos


sincronizados del mismo objeto a la vez. Cuando un hilo está ejecutando un método sincronizado
de un objeto, los demás hilos que invoquen a métodos sincronizados para el mismo objeto se
bloquean hasta que el primer hilo termine con la ejecución del método.
Veamos mediante clases como sería el ejemplo de compartir una cuenta sin usar métodos
sincronizados. Se define la clase Cuenta, defme un atributo saldo y tres métodos, uno devuelve el
96 Programación de servicios y procesos

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 ;

Cuenta (int s) { saldo = s; } //inicializa saldo actual


int getSaldo () { return saldo; //devuelve el saldo
voi d resta r(int cant idad) · { //se resta la cantidad al saldo
saldo = saldo - cantidad;

void RetirarDinero(int cant , String nom) {


i f (getSaldo () >= cant) {
System . out .println (nom + ": SE VA A RETIRAR SALDO (ACTUAL ES: " +
getSaldo () + ") " ) ;
try {
Thread .sleep(SOO);
} catch (InterruptedException ex) { }

restar(cant) ; //resta la cantidad del saldo

System . out . println( " \t " + nom +


"retira=>"+ cant +"ACTUAL("+ getSaldo() + " ) " ) ;
else {
System . out . println(nom +
" No puede retirar dinero, NO HAY SALDO ( "+ getSaldo () + " )" ) ;
}
if (getSaldo() < 0) {
System . out . println( " SALDO NEGATIVO=> " + getSaldo()) ;
}
}//RetirarDinero
}//Cuenta

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;

public void run()


for (int x = 1; x<= 4; x++) {
c . RetirarDinero(lO , getName ( )) ;

}// 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 (} ;

Al ejecutar puede ocurrir que se permita retirar saldo cuando este es 0:


Ana : SE VA A RETIRAR SALDO (ACTUAL ES: 40}
Juan : SE VA A RETIRAR SALDO (ACTUAL ES: 40}
Ana retira =>10 ACTUAL(30 }
Ana : SE VA A RETIRAR SALDO (ACTUAL ES: 30}
Juan retira =>10 ACTUAL(20}
Juan : SE VA A RETIRAR SALDO (ACTUAL ES : 20}
Ana retira =>10 ACTUAL(10}
Ana : SE VA A RETIRAR SALDO (ACTUAL ES : 10}
Jua.n retira =>1 0 ACTOAL (O ) ~~~~~~~~~~--. . . .~
Juan No puede retirar dinero , NO HAY SALDO(O}
Juan No puede retirar dinero , NO HAY SALDO(O}
Ana retira =>10 ACTUAL( - 10}
SALDO NEGATIVO => -10
Ana No puede r e t irar dinero , NO HAY SALD0(-10}
SALDO NEGATIVO => -1 0

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 .. .

El método RetirarDineroO en nuestro ejemplo quedaría así:


98 Programacíón de servicios y procesos

synchronized void RetirarDinero(int cant, Strinq nom) {


//las mismas instrucciones que antes

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.

2.7.3. BLOQUEO DE HILOS


En el siguiente ejemplo creamos una clase que define un método que recibe un String y lo
pinta:
class ObjetoCompartido {
public void PintaCadena (String s) {
System . out .print(s) ;

}// 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

La clase HiloCadena extiende Thread ; en su método runO invoca al método PintaCadenaO


del objeto compartido dentro de un bucle for:
class HiloCadena extends Thread {
prívate ObjetoCompartido objeto;
String ead;

public HiloCadena (ObjetoCompartido e , String s) {


this . objeto = e ;
this .cad=s;

public void run() {


for (int j = O; j < 10 ; j++ )
objeto.PintaCadena(cad);
}//run

}//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.

En el ejemplo, dentro del bloque sincronizado y después de pintar la cadena se invocará al


método notifyQ del objeto compartido para despertar al hilo que esté esperando el objeto
(notifyAJJQ cuando varios hilos esperan el objeto). Inmediatamente después se llama al método
waitO del objeto para que el hilo quede suspendido y el que estaba en espera tome el objeto para
pintar la cadena; el hilo permanecerá suspendido hasta que se produzca un notifyQ sobre el
objeto. El último notifyQ es necesario para que los hilos finalicen correctamente y ninguno
quede bloqueado:
public void run() (
synchronized (objeto)

for (int j = O; j < 10; j++) {


objeto . PintaCadena(cad) ;
objeto.notify(); //aviso que ya he usado el objeto
try {
objeto . wait() ;//esperar a que llegue un notify
} catch (InterruptedException e) {
e.printStackTrace();
}
}//for

objeto . notify() ; //despertar a todos los wait sobre el objeto

}//fin bloque synchronized

Systern . out . print("\n " +cad + " finalizado " );


}//run

La ejecución del ejemplo muestra la siguiente salida:


A B A B A B A B A B A B A B A B A B A B
B fina lizado
A finalizado

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.

2.7.4. El MODELO PRODUCTOR-CONSUMIDOR


Un problema típico de sincronización es el que representa el modelo Productor-
Consumidor. Se produce cuando uno o más hilos producen datos a procesar y otros hilos los
consumen. El problema surge cuando el productor produce datos más rápido que el consumidor
los consuma, dando lugar a que el consumidor se salte algún dato. Igualmente, el consumidor
puede consumir más rápido que el productor produce, entonces el consumidor puede recoger
varias veces el mismo dato o puede no tener datos que recoger o puede detenerse, etc.
Capítulo 2. Programación muttihilo 101

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;

public Productor(Cola e, int n) {


cola = e;
this.n = n;

public void run() {


for (int i = O; i < 5; i++) {
cola.put(i); //pone el número
System . out . println(i + "=>Productor : " + n
+ ", produce : " + i) ;
try {
sleep(lOO);
} catch (InterruptedException e) { }

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;

public Consumi dor(Cola e , int n) {


cola = e;
this.n = n ;

public void run() {


int valor = O;
for (int i = O; i < 5; i++) {
valor= cola.get(); //recoge el número
System.out. println(i + "=>Consumidor: " + n
+ ", consume : " +valor);
102 Programación de servicios y procesos

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

public int get() {


if(disponible) //hay número en l a cola
disponible = false ; //se pone cola vacía
return numero; //se devuelve

return -1 ; //no hay número disponible, cola vacía

public v o id put (int v alor) {


numero= valor ; //coloca valor en la cola
disponible = true; //disponible para consumir , cola l lena

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 () ;

Al ejecutar se produce la siguiente salida, en la que se puede observar que el consumidor va


más rápido que el productor (al que se le puso un sleepO) y no consume todos los números
cuando se producen; el numerito de la izquierda de cada fila representa la iteración:
O=>Productor1 : produce : o
0=>Consumidor1 : consume : o
1=>Consumidorl : con sume: -1
2=>Consumi dorl: consume : -1
3=>Consumidor1: consume : -1
4=>Consumidor1 : consume : -1
1=>Productor1 produce : 1
2=>Productor1 produce : 2
3=>Productor1 produce: 3
4=>Productor 1 produce : 4
Capítulo 2. Programación multihilo 103

En la iteración O, el productor produce un O e inmediatamente el consumidor lo consume, la


cola se queda vacía. En la iteración 1 el consumidor consume -1 que indica que la cola está vacía
porque la iteración 1 del productor no se ha producido. En la iteración 2 pasa lo mismo el
consumidor toma - 1 porque el productor aún no ha dejado valor en la cola. Y así sucesivamente.
La salida deseada es la siguiente: en cada iteración el productor produce un número (llena la
cola) e inmediatamente el consumidor lo consume (la vacía):

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

public synchronized void put(int va lor) {


//instrucciones

En segundo lugar, es necesario mantener una coordinación entre el productor y el consumidor


de forma que cuando el productor ponga un número en la cola avise al consumidor de que la cola
está disponible para recoger su valor; y al revés, cuando el consumidor recoja el valor de la cola
debe avisar al productor de que la cola ha quedado vacía. A su vez, el consumidor deberá
esperar hasta que la cola se llene y el productor esperará hasta que la cola esté nuevamente vacía
para poner otro número.
Para mantener esta coordinación usamos los métodos wait(), notify() y notifyAil() vistos
anteriormente. Estos sólo pueden ser invocados desde dentro de un método sincronizado o dentro
de un bloque o una sentencia sincronizada.
El método getO tiene que esperar a que la cola se llene (Figura 2.6) , esto se realiza en el
bucle while: mientras la cola esté vacía, es decir disponible esfalse (while (!disponible)), espero
(wait). Se sale del bucle cuando llega un valor, en este caso se vuelve a poner disponible aja/se
(porque se va a devolver quedando la cola vacía de nuevo), se notifica a todos los hilos que
comparten el objeto este hecho y se devuelve el valor (Figura 2. 7):
104 Programación de servicios y procesos

Cola vacía

COLA •••• ••••


.... ····~
get()
wait()
Productor Consumidor
Figura 2.6. Método get() espera.

Cola llena

put() •• COLA
.. ·······
•••• •• wait()
Productor
disponible=false
notifyAII()

Consumidor
Figura 2.7. Método get() devuelve valor, put() espera.

1\létodo gctO sincronizado Método gctO anterior


publ ic s ynchro nize d int get( ) {
whi l e ( !dispo nLble ) {
try { p ublic int get() {
wait (); if(disponible) {
} catch (Inter ruptedException e) { } disponible=false;
r etu r n nume ro
//visual iza r número
di sponible = false ; return - 1 ;
no tify ();
return numero ;

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:

Método put() sincronizado :\létodo putO anterior


public s ynchronized void put(int valo r) {
while (disponLble){
t ry {
wait ();
public void put(int va lor)
} catch (I nter r uptedException e ) { } numero = valor;
disponibl e =true ;
nume r o = val or ;
dispon ible = t rue ;
//visualiza r número
n o tif y() ;
Capitulo 2. Programación multihilo 105

La ejecución del ejemplo no siempre muestra la salida esperada, aunque las iteraciones sí
aparecen correctamente:

O=> Product or : 1, p roduce : o


O=>Consumidor: 1, consume : o
1=>Productor : 1, produce : 1
1=>Consumi dor: 1, consume : 1
2=>Con s umidor: 1, consume : 2
2=>Product or : 1, p roduce : 2
3=>Productor : 1, produce : 3
3=>Consumidor: 1, consume : 3
4=>Consumidor : 1, consume : 4
4=>Productor : 1, produce : 4

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

--- Figura 2.8. Ejercicio 5.

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

Figura 2.9. Ejercicio 6.

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.

~ EJEClTTAI\ SUSPENOER Y REANUDAR HilOS O X [¡¡ EJEOITAI\ SUSPENDER YREANUDAR ~OS O X

Comenzar Proceso

Reanudar

L Suspender su~-.J

23 28

HILOl HIL02 Hiol Corriendo Hilo2 suspendido

FIIIÜZllr Pn>ceso ::::J


Figura 2.1O. Ejercicio 8.

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

F Interrumpir ] lnttrrumpi_r----.~ Ctnterr~

20 2.1

HILOl Hll02 Hiol Corriendo H1o2 ilterrumoido

~burProceso Frnalzar Proceso j


Figura 2.19. Ejercicio 9.
Capitulo 2. Programación multihilo 109

10°) Usando el modelo productor-consumidor, crea un productor que lea caracteres de un


fichero de texto cuyo nombre se pasará en el constructor. Y un consumidor que obtenga los datos
que produce el productor y los visualice en pantalla. Muestra al fmal del proceso del productor y
del consumidor un mensaje indicando que el proceso ha finalizado. Prueba el programa con
varios consumidores, ¿fmalizan el proceso todos los consumidores?; utiliza el método getStateO
para comprobar el estado de los consumidores cuando el productor finaliza. Intenta que todos los
consumidores finalicen correctamente.

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!!!

También podría gustarte