You are on page 1of 12

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 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: detroy(), stop(), suspend() ... Pero, si nos paramos a mirar laAPI 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. 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(); 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 de datos) y es posbible 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 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 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 filosfa de la orientacin a objetos, encapsula ms las cosas. El fichero requiere sincronizacin, pero ese conocimiento slo lo tiene la claseControladorFichero. 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. 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. 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 llamdas a wait() lanzan excepciones que hay que capturar. Todas las llamadas que pongamos aqu debera 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); 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 implemetar 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 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. 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 lalistaSincronizada. 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();

package Hilos;

/** * * @author itsav21 */ public class ThreadDemo implements Runnable { ThreadDemo() { Thread ct = Thread.currentThread(); Thread t = new Thread(this, "demo Thread"); System.out.println("hilo actual: " + ct);

System.out.println("Hilo creado: " + t); t.start(); try { Thread.sleep(3000); } catch (Exception e){

System.out.println("Interrumpido"); } System.out.println("saliendo del hilo main"); } public void run() { try { for (int y = 5; y > 0; y--){ System.out.println(" " + "5"); Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println("hijo interrumpido"); } System.out.println("saliendo del hilo hijo"); } public static void main (String args []) { new ThreadDemo(); } }

You might also like