Vous êtes sur la page 1sur 11

Fuente: http://www.chuidiang.com/java/hilos/hilos_java.

php

Hilos en Java
A veces necesitamos que nuestro programa Java realice varias cosas simultneamente.
Otras veces tiene que realizar una tarea muy pesada, por ejemplo, consultar en el listn
telefnico todos los nombres de chica que tengan la letra n, que tarda mucho y no
deseamos que todo se quede parado mientras se realiza dicha tarea. Para conseguir que
Java haga varias cosas a la vez o que el programa no se quede parado mientras realiza una
tarea compleja, tenemos los hilos (Threads).

Crear un Hilo
Crear un hilo en java es una tarea muy sencilla. Basta heredar de la clase Thread y definir
el mtodo run(). Luego se instancia esta clase y se llama al mtodo start() para que
arranque el hilo. Ms o menos esto
public MiHilo extends Thread
{
public void run()
{
// Aqu el cdigo pesado que tarda mucho
}
};
...
MiHilo elHilo = new MiHilo();
elHilo.start();
System.out.println("Yo sigo a lo mio");

Listo. Hemos creado una clase MiHilo que hereda de Thread y con un mtodo run(). En el
mtodo run() pondremos el cdigo que queremos que se ejecute en un hilo separado.
Luego instanciamos el hilo con un new MiHilo() y lo arrancamos con elHilo.start(). El
System.out que hay detrs se ejecutar inmediatamente despus del start(), haya
terminado o no el cdigo del hilo.

Detener un hilo
Suele ser una costumbre bastante habitual que dentro del mtodo run() haya un bucle
infinito, de forma que el mtodo run() no termina nunca. Por ejemplo, supongamos un
1

chat. Cuando ests chateando, el programa que tienes entre tus manos est haciendo dos
cosas simultneamente. Por un lado, lee el teclado para enviar al servidor del chat todo lo
que t escribas. Por otro lado, est leyendo lo que llega del servidor del chat para
escribirlo en tu pantalla. Una forma de hacer esto es "por turnos"
while (true)
{
leeTeclado();
enviaLoLeidoAlServidor();
leeDelServidor();
muestraEnPantallaLoLeidoDelServidor();
}

Esta, desde luego, es una opcin, pero sera bastante "cutre", tendramos que hablar por
turnos. Yo escribo algo, se le enva al servidor, el servidor me enva algo, se pinta en
pantalla y me toca a m otra vez. Si no escribo nada, tampoco recibo nada. Quizs sea
buena opcin para los que no son giles leyendo y escribiendo, pero no creo que le guste
este mecanismo a la mayora de la gente.
Lo normal es hacer dos hilos, ambos en un bucle infinito, leyendo (del teclado o del
servidor) y escribiendo (al servidor o a la pantalla). Por ejemplo, el del teclado puede ser
as
public void run()
{
while (true)
{
String texto = leeDelTeclado();
enviaAlServidor(texto);
}
}

Esta opcin es mejor, dos hilos con dos bucles infinitos, uno encargado del servidor y otro
del teclado.
Ahora viene la pregunta del milln. Si queremos detener este hilo, qu hacemos?. Los
Thread de java tienen muchos mtodos para parar un hilo: destroy(), stop(), suspend() ...
Pero, si nos paramos a mirar la API de Thread, nos llevaremos un chasco. Todos esos
mtodos son inseguros, estn obsoletos, desaconsejados o las tres cosas juntas.
Cmo paramos entonces el hilo?
La mejor forma de hacerlo es implementar nosotros mismos un mecanismo de parar, que
lo nico que tiene que hacer es terminar el mtodo run(), saliendo del bucle.
2

Un posible mecanismo es el siguiente


public class MiHilo extends Thread
{
// boolean que pondremos a false cuando queramos parar el hilo
private boolean continuar = true;
// metodo para poner el boolean a false.
public void detenElHilo()
{
continuar=false;
}
// Metodo del hilo
public void run()
{
// mientras continuar ...
while (continuar)
{
String texto = leeDelTeclado();
enviaAlServidor(texto);
}
}
}

Simplemente hemos puesto en la clase un boolean para indicar si debemos seguir o no


con el bucle infinito. Por defecto a true. Luego hemos aadido un mtodo para cambiar el
valor de ese boolean a false. Finalmente hemos cambiado la condicin del bucle que
antes era true y ahora es continuar.
Para parar este hilo, es sencillo
MiHilo elHilo = new MiHilo();
elHilo.start();
// Ya tenemos el hilo arrancado
...
// Ahora vamos a detenerlo
elHilo.detenElHilo();

Sincronizacin de hilos
Cuando en un programa tenemos varios hilos corriendo simultneamente es posible que
varios hilos intenten acceder a la vez a un mismo sitio (un fichero, una conexin, un array
3

de datos) y es posible que la operacin de uno de ellos entorpezca la del otro. Para evitar
estos problemas, hay que sincronizar los hilos. Por ejemplo, si un hilo con vocacin de
Cervantes escribe en fichero "El Quijote" y el otro con vocacin de Shakespeare escribe
"Hamlet", al final quedarn todas las letras entremezcladas. Hay que conseguir que uno
escriba primero su Quijote y el otro, luego, su Hamlet.

Sincronizar usando un objeto


Imagina que escribimos en un fichero usando una variable fichero de tipo PrintWriter.
Para escribir uno de los hilos har esto
fichero.println("En un lugar de la Mancha...");
Mientras que el otro har esto
fichero.println("... ser o no ser ...");
Si los dos hilos lo hacen a la vez, sin ningn tipo de sincronizacin, el fichero al final puede
tener esto
En un ... ser lugar de la o no Mancha ser ...
Para evitarlo debemos sincronizar los hilos. Cuando un hilo escribe en el fichero, debe
marcar de alguna manera que el fichero est ocupado. El otro hilo, al intentar escribir, lo
ver ocupado y deber esperar a que est libre. En java esto se hace fcilmente. El cdigo
sera as
synchronized (fichero)
{
fichero.println("En un lugar de la Mancha...");
}
y el otro hilo
synchronized (fichero)
{
fichero.println("... ser o no ser ...");
}
Al poner synchronized(fichero) marcamos fichero como ocupado desde que se abren las
llaves de despus hasta que se cierran. Cuando el segundo hilo intenta tambin su
4

synchronized(fichero), se queda ah bloqueado, en espera que de que el primero termine


con fichero. Es decir, nuestro hilo Shakespeare se queda parado esperando en el
synchronized(fichero) hasta que nuestro hilo Cervantes termine.
synchronized comprueba si fichero est o no ocupado. Si est ocupado, se queda
esperando hasta que est libre. Si est libre o una vez que est libre, lo marca como
ocupado y sigue el cdigo.
Este mecanismo requiere colaboracin entre los hilos. El que hace el cdigo debe
acordarse de poner synchronized siempre que vaya a usar fichero. Si no lo hace, el
mecanismo no sirve de nada.

Mtodos sincronizados
Otro mecanismo que ofrece java para sincronizar hilos es usar mtodos sincronizados.
Este mecanismo evita adems que el que hace el cdigo tenga que acordarse de poner
synchronized.
Imagina que encapsulamos fichero dentro de una clase y que ponemos un mtodo
synchronized para escribir, tal que as
public class ControladorDelFichero
{
private PrintWriter fichero;
public ControladorFichero()
{
// Aqui abrimos el fichero y lo dejamos listo
// para escribir.
}
public synchronized void println(String cadena)
{
fichero.println(cadena);
}
}
Una vez hecho esto, nuestros hilos Cervantes y Shakespeare slo tienen que hacer esto
5

ControladorFichero control = new ControladorFichero();


...
// Hilo Cervantes
control.println("En un lugar de la Mancha ...");
...
// Hilo Shakespeare
control.println("... ser o no ser ...");
Al ser el mtodo println() synchronized, si algn hilo est dentro de l ejecutando el
cdigo, cualquier otro hilo que llame a ese mtodo se quedar bloqueado en espera de
que el primero termine.
Este mecanismo es ms mejor porque, siguiendo la filosofa de la orientacin a objetos,
encapsula ms las cosas. El fichero requiere sincronizacin, pero ese conocimiento slo lo
tiene la clase ControladorFichero. Los hilos Cervantes y Shakespeare no saben nada del
tema y simplemente se ocupan de escribir cuando les viene bien. Tampoco depende de la
buena memoria del programador a la hora de poner el synchronized(fichero) de antes.
Otros objetos que necesitan sincronizacin
Hemos puesto de ejemplo un fichero, pero requieren sincronizacin en general cualquier
entrada y salida de datos, como pueden ser ficheros, sockets o incluso conexiones con
bases de datos.
Tambin pueden necesitar sincronizacin almacenes de datos en memoria, como
LinkedList, ArrayList, etc. Imagina, por ejemplo, en una LinkedList que un hilo est
intentando sacar por pantalla todos los datos
LinkedList lista = new LinkedList();
...
for (int i=0;i<lista.size(); i++)
System.out.println(lista.get(i));
Estupendo y maravilloso pero ... qu pasa si mientras se escriben estos datos otro hilo
borra uno de los elementos?. Imagina que lista.size() nos ha devuelto 3 y justo antes de
intentar escribir el elemento 2 (el ltimo) viene otro hilo y borra cualquiera de los
elementos de la lista. Cuando intentemos el lista.get(2) nos saltar una excepcin porque
la lista ya no tiene tantos elementos.
6

La solucin es sincronizar la lista mientras la estamos usando


LinkedList lista = new LinkedList();
...
synchronized (lista)
{
for (int i=0;i<lista.size(); i++)
System.out.println(lista.get(i));
}
adems, este tipo de sincronizacin es la que se requiere para mantener "ocupado" el
objeto lista mientras hacemos varias llamadas a sus mtodos (size() y get()), no queda
ms remedio que hacerla as. Por supuesto, el que borra tambin debe preocuparse del
synchronized.

Esperando datos: wait() y notify()


A veces nos interesa que un hilo se quede bloqueado a la espera de que ocurra algn
evento, como la llegada de un dato para tratar o que el usuario termine de escribir algo en
una interface de usuario. Todos los objetos java tienen el mtodo wait() que deja
bloqueado al hilo que lo llama y el mtodo notify(), que desbloquea a los hilos bloqueados
por wait(). Vamos a ver cmo usarlo en un modelo productor/consumidor.

Bloquear un hilo
Antes de nada, que quede claro que las llamadas a wait() lanzan excepciones que hay que
capturar. Todas las llamadas que pongamos aqu deberan estar en un bloque try-catch,
as
try
{
// llamada a wait()
}
catch (Exception e)
{
....
}

pero para no liar mucho el cdigo y mucho ms importante, no auto darme ms trabajo
de escribir de la cuenta, no voy a poner todo esto cada vez. Cuando hagas cdigo, habr
que ponerlo.
Vamos ahora a lo que vamos...
Para que un hilo se bloquee basta con que llame al mtodo wait() de cualquier objeto. Sin
embargo, es necesario que dicho hilo haya marcado ese objeto como ocupado por medio
de un synchronized. Si no se hace as, saltar una excepcin de que "el hilo no es
propietario del monitor" o algo as.
Imaginemos que nuestro hilo quiere retirar datos de una lista y si no hay datos, quiere
esperar a que los haya. El hilo puede hacer algo como esto
synchronized(lista);
{
if (lista.size()==0)
lista.wait();
dato = lista.get(0);
lista.remove(0);
}
En primer lugar hemos hecho el synchronized(lista) para "apropiarnos" del objeto lista.
Luego, si no hay datos, hacemos el lista.wait(). Una vez que nos metemos en el wait(), el
objeto lista queda marcado como "desocupado", de forma que otros hilos pueden usarlo.
Cuando despertemos y salgamos del wait(), volver a marcarse como "ocupado."
Nuestro hilo se desbloquer y saldr del wait() cuando alguien llame a lista.notify(). Si el
hilo que mete datos en la lista llama luego a lista.notify(), cuando salgamos del wait()
tendremos datos disponibles en la lista, as que nicamente tenemos que leerlos (y
borrarlos para no volver a tratarlos la siguiente vez). Existe otra posibilidad de que el hilo
se salga del wait() sin que haya datos disponibles, pero la veremos ms adelante.
Notificar a los hilos que estn en espera
Hemos dicho que el hilo que mete datos en la lista tiene que llamar a lista.notify(). Para
esto tambin es necesario apropiarnos del objeto lista con un synchronized. El cdigo del
hilo que mete datos en la lista quedar as
synchronized(lista)
{
lista.add(dato);
8

lista.notify();
}
Listo, una vez que hagamos esto, el hilo que estaba bloqueado en el wait() despertar,
saldr del wait() y seguir su cdigo leyendo el primer dato de la lista.

wait() y notify() como cola de espera


wait() y notify() funcionan como una lista de espera. Si varios hilos van llamando a wait()
quedan bloqueados y en una lista de espera, de forma que el primero que llam a wait()
es el primero de la lista y el ltimo es el tlimo.
Cada llamada a notify() despierta al primer hilo en la lista de espera, pero no al resto, que
siguen dormidos. Necesitamos por tanto hacer tantos notify() como hilos hayan hecho
wait() para ir despertndolos a todos de uno en uno.
Si hacemos varios notify() antes de que haya hilos en espera, quedan marcados todos esos
notify(), de forma que los siguientes hilos que hagan wait() no se quedaran bloqueados.
En resumen, wait() y notify() funcionan como un contador. Cada wait() mira el contador y
si es cero o menos se queda bloqueado. Cuando se desbloquea decrementa el contador.
Cada notify() incrementa el contador y si se hace 0 o positivo, despierta al primer hilo de
la cola.
Un smil para entenderlo mejor. Una mesa en la que hay gente que pone caramelos y
gente que los recoge. La gente son los hilos. Los que van a coger caramelos (hacen wait())
se ponen en una cola delante de la mesa, cogen un caramelo y se van. Si no hay
caramelos, esperan que los haya y forman una cola. Otras personas ponen un caramelo en
la mesa (hacen notify()). El nmero de caramelos en la mesa es el contador que
mencionabamos.

Modelo Productor/Consumidor
Nuevamente y como comentamos en sincronizar hilos, es buena costumbre de
orientacin a objetos "ocultar" el tema de la sincronizacin a los hilos, de forma que no
dependamos de que el programador se acuerde de implementar su hilo correctamente
(llamada a synchronized y llamada a wait() y notify()).
Para ello, es prctica habitual meter la lista de datos dentro de una clase y poner dos
mtodos synchronized para aadir y recoger datos, con el wait() y el notify() dentro.
El cdigo para esta clase que hace todo esto puede ser as
9

public class MiListaSincronizada


{
private LinkedList lista = new LinkedList();
public synchronized void addDato(Object dato)
{
lista.add(dato);
lista.notify();
}
public synchronized Object getDato()
{
if (lista.size()==0)
wait();
Object dato = lista.get(0);
lista.remove(0);
return dato;
}
}
Listo, nuestros hilos ya no deben preocuparse de nada. El hilo que espera por los datos
hace esto
Object dato = listaSincronizada.getDato();
y eso se quedar bloqueado hasta que haya algn dato disponible. Mientras, el hilo que
guarda datos slo tiene que hacer esto otro
listaSincronizada.addDato(dato);

Interrumpir un hilo
Comentamos antes que es posible que un hilo salga del wait() sin necesidad de que nadie
haga notify(). Esta situacin se da cuando se produce algn tipo de interrupcin. En el
caso de java es fcil provocar una interrupcin llamando al mtodo interrupt() del hilo.
Por ejemplo, si el hiloLector est bloqueado en un wait() esperando un dato, podemos
interrumpirle con
hiloLector.interrupt();
El hiloLector saldr del wait() y se encontrar con que no hay datos en la lista. Sabr que
alguien le ha interrumpido y har lo que tenga que hacer en ese caso.
10

Por ejemplo, imagina que tenemos un hilo lectorSocket pendiente de un socket (una
conexin con otro programa en otro ordenador a travs de red) que lee datos que llegan
del otro programa y los mete en la listaSincronizada.
Imagina ahora un hilo lectorDatos que est leyendo esos datos de la listaSincronizada y
tratndolos.
Qu pasa si el socket se cierra?. Imagina que nuestro programa decide cerrar la conexin
(socket) con el otro programa en red porque se han enfadado y ya no piensan hablarse
nunca ms. Una vez cerrada la conexin, el hilo lectorSocket puede interrumpir al hilo
lectorDatos. Este, al ver que ha salido del wait() y que no hay datos disponibles, puede
suponer que se ha cerrado la conexin y terminar.
El cdigo del hilo lectorDatos puede ser as
while (true)
{
if (listaSincronizada.size() == 0)
wait();
// Debemos comprobar que efectivamente hay datos.
if (listaSincronizada.size() > 0)
{
// Hay datos, los tratamos
Object dato=listaSincronizada.get(0);
listaSincronizada.remove(0);
// tratar el dato.
}
else
{
// No hay, datos se debe haber cerrado la conexion
// as que nos salimos.
return;
}
}
y el hilo lectorSocket, cuando cierra la conexin, debe hacer
socket.close();
lectorDatos.interrupt();

11

Vous aimerez peut-être aussi