Hilos en Java
Hilos en 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
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.
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
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.
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
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