Sei sulla pagina 1di 21

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 mtodorun() 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 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. 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 atrue. 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(); Seguimos ahora sincronizando hilos... Estadsticas y comentarios

Numero de visitas desde el 4 Feb 2007:

Esta pagina este mes: 1740 Total de esta pagina: 204120 Total del sitio: 12788464

Introduccin

Un hilo es un proceso que se est ejecutando en un momento determinado en nuestro sistema operativo, como cualquier otra tarea, esto se realiza directamente en el procesador. Existen los llamados demonios que son los procesos que define el sistema en s para poder funcionar y otros que llamaremos los hilos definidos por el usuario o por el programador, estos ltimos son procesos a los que el programador define un comportamiento e inicia en un momento especfico.

Normalmente los hilos de un proceso (en este contexto el proceso es lo que se suele llamar as en el mbito de sistemas operativos) suelen tener acceso a todos los recursos disponibles al proceso, es decir, actan sobre una memoria compartida.

Por defecto, un hilo nuevamente creado y lanzado an siendo activado as no hace nada. Sin embargo, los hilos se ejecutan durante un tiempo infinito y hay que abortar el programa de forma bruta: control+C en el terminal.

En Java, el proceso que siempre se ejecuta es el llamado main que es a partir del cual se inicia prcticamente todo el comportamiento de nuestra aplicacin, y en ocasiones a la aplicacin le basta con este solo proceso para funcionar de manera adecuada, sin embargo, existen algunas aplicaciones que requieren ms de un proceso (o hilo) ejecutndose al mismo tiempo (multithreading), por ejemplo, se tiene una aplicacin de una tienda departamental de la cual se actualizan los precios y mercancas varias veces al da a travs de la red, se verifican los nuevos descuentos y dems pero que a su vez es la encargada de registrar las compras y todos movimientos que se realice con la mercanca dentro de la tienda, si se decide que dicha aplicacin trabajar de la manera simple y con un solo proceso (o hilo), el trabajo de la actualizacin de precios y mercancas debe de finalizar antes de que alguien pueda hacer algn movimiento con un producto dentro de la tienda, o viceversa, ya que la aplicacin no es capaz de mantener el proceso de actualizacin en segundo plano mientras se registra un movimiento. Si se toma este modelo mono-hilo el tiempo y dinero que se perder dentro de la tienda ser muchsimo mayor

comparando con un modelo multi-hilo. En un modelo multi-hilo se pueden realizar todas las actualizaciones en segundo plano mientras se registra una o ms ventas o movimientos, cada proceso independiente del otro viviendo o ejecutndose al mismo tiempo dentro de la misma aplicacin.

Al hablar de multi-hilo pudiera parecer que necesitamos ms de un procesador para realizar dichas tareas pero no es as, el procesador mismo junto con la mquina virtual de Java gestionan el flujo de trabajo y dan la impresin de que se puede ejecutar ms de algn proceso al mismo tiempo (aunque en trminos estrictos eso no es posible), de cualquier manera no ahondaremos en el funcionamiento del procesador, basta con entender que en Java, dos o ms procesos pueden ejecutarse al mismo tiempo dentro de una misma aplicacin y para ello son necesarios los Threads o hilos.

Ahora que ya entendemos lo que son los hilos pasaremos a una definicin un poco ms especfica de Java. En Java un hilo o Thread puede ser 2 cosas:

Una instancia de la clase java.lang.Thread Un proceso en ejecucin Una instancia de la clase java.lang.Thread, no es ms que cualquier otro objeto, con variables y mtodos predefinidos. Un proceso en ejecucin es un proceso individual que realiza una tarea o trabajo, tiene su propia pila de informacin independiente a la de la aplicacin principal.

Creacin de un hilo

Un hilo o proceso en Java comienza con una instancia de la clase java.lang.Thread, si analizamos la estructura de dicha clase podremos encontrar bastantes mtodos que nos ayudan a controlar el comportamiento de los hilos, desde crear un hilo, iniciarlo, pausar su ejecucin, etc. Aquellos mtodos que siempre tenemos que tener presentes con respecto a los hilos son: start(), yield(), sleep() y run() . La accin sucede dentro del mtodo run(), digamos que el cdigo que se encuentra dentro de dicho mtodo es el trabajo por hacer, por lo tanto, si queremos realizar diversas operaciones cada una simultnea pero de manera independiente, tendremos varias clases, cada una con su respectivo mtodo run(). Dentro del mtodo run() puede haber llamados a otros mtodos como en cualquier otro mtodo comn, pero la pila de ejecucin del nuevo proceso siempre comenzar a partir de la llamada al mtodo run().

Definir un nuevo hilo

Para definir e instanciar un nuevo Thread (hilo, proceso) existen 2 formas:

Extendiendo (o heredando) a la clase java.lang.Thread Implementando la interfaz Runnable Normalmente en el trabajo diario es ms recomendable el implementar la interfaz Runnable, en lugar de extender la clase java.lang.Thread debido a que una clase solamente puede heredar o extender otra sola clase pero puede implementar muchas interfaces. Si extendemos de la clase java.lang.Thread no podremos extender o heredar ninguna otra, pero si implementamos la interfaz Runnable, podremos heredar cualquier otra clase e implementar muchas otras interfaces sin perder el comportamiento de la nuestra. En cualquiera de los 2 casos, ya sea heredando de java.lang.Thread o implementando Runnable, al definir e instanciar un nuevo hilo, necesitaremos de redefinir el mtodo run(), veamos cmo hacerlo.

Extendiendo java.lang.Thread

class MiHilo extends Thread{

public void run() {

System.out.println(Ejecucin dentro del hilo);

Nuevamente, esto no es recomendable ya que al heredar la clase Thread, no es posible heredar nada ms. Es necesario tomar en cuenta que se puede sobrecargar el mtodo run() sin ningn problema como se muestra a continuacin:

class MiHilo extends Thread {

public void run() {

System.out.println(Ejecucin del hilo);

public void run(String s) {

System.out.println(La cadena ingresada es + s);

Sin embargo, al realizar esto, no se estar utilizando el nuevo mtodo public void run (String s) en un proceso separado, es un simple mtodo comn y corriente como cualquier otro que se tiene que mandar a llamar de manera independiente ya que los hilos trabajan con un mtodo run() sin argumentos.

Implementando la interfaz Runnable

class MiHilo implements Runnable {

public void run() {

System.out.println(Trabajo por hacer dentro de MiHilo);

NOTA: El comportamiento del hilo es independiente de como sea definido (por medio de extends Thread o implements Runnable).

Instanciando un hilo o Thread

Debemos recordar que cada hilo de ejecucin es una instancia de la clase Thread, independientemente de si tu mtodo run() est dentro de una subclase de Thread o en una implementacin de la interfaz Runnable, se necesita un objeto tipo Thread para realizar el trabajo.

Si se ha extendido la clase Thread, el instanciar el hilo es realmente simple:

MiHilo h = new MiHilo();

Implementado la interfaz Runnable, es un poquito ms complicado, pero solo un poco:

MiHilo h = new MiHilo();

Thread t = new Thread(h);

El pasar un solo objeto tipo MiHilo a varios objetos tipo Thread significa que el trabajo que se encuentra dentro del mtodo run() de MiHilo se realizar en diversas ocasiones, o lo que es lo mismo, varios hilos realizarn el mismo trabajo. Hasta ahora sabemos cmo crear el objeto tipo Thread e instanciarlo y sabemos que debe de estar en un estado vivo para que realice su funcin, para ello se utiliza el mtodo start().

hilo.start();

Se debe de llamar al mtodo start() a travs de una instancia de la clase Thread. Una vez que llamamos al mtodo start() sucede lo siguiente:

El hilo o proceso cambia de estado nuevo o new a estado de ejecucin o runnable. Cuando el hilo tenga su turno de ejecutarse, el mtodo run() del objeto al que refiere se ejecuta. Ejemplo completo:

public class Hilos { public static void main(String[] args) {

System.out.println(Dentro de main); HiloNuevo hn = new HiloNuevo(); Thread nuevoHilo = new Thread(hn); nuevoHilo.start(); } }

class HiloNuevo implements Runnable { public HiloNuevo() { System.out.println(Comenzando un HiloNuevo);

public void run() {

System.out.println(Llamando al mtodo run de HiloNuevo);

for(int i=0;i<5;i++) {

System.out.println(i); } System.out.println(Terminando el trabajo); } }

Es importante tomar en cuenta que al llamar el mtodo start(), se ejecuta el trabajo definido en el mtodo run() en un nuevo e independiente proceso, de igual manera podemos llamar directamente al mtodo run() y el mismo trabajo se ejecutar pero no comenzar un proceso independiente y no tendr sentido que utilicemos los hilos.

Para saber qu hilo se encuentra en ejecucin en un momento determinado, existe el mtodo esttico Thread.currentThread().getName() el cual te devuelve un valor de tipo cadena con el nombre del hilo en ejecucin, si no has definido un nombre con el mtodo setName(), de igual manera el proceso lo tendr, algo muy parecido a Thread-0. Hasta el momento hemos visto 3 tipos de estado de un hilo: nuevo (new), en ejecucin (runnable) y muerto (dead). Existen algunos otros estados de un hilo que analizaremos ms adelante.

El programador de hilos

El programador de hilos que es el encargado de decidir qu hilo es el que se va a ejecutar por el procesador en un momento determinado y cundo es que debe de parar o pausar su ejecucin. Asumiendo que se tiene un equipo de un solo procesador, solo un proceso puede estar corriendo en un momento dado, como lo hemos mencionado anteriormente, la idea de multi-proceso o multithreading es meramente aparente. Para que un hilo sea elegible para ser ejecutado, este debe de estar en estado en ejecucin (runnable).

Los mtodos de la clase java.lang.Thread

Algunos mtodos de esta clase nos pueden ayudar a influenciar al programador de hilos a tomar una decisin sobre qu hilo ejecutar en qu momento. Los mtodos son los siguientes:

public static void sleep(long milisegundos) throws InterruptedException public static void yield() public final void join() throws InterruptedException public final void setPriority(int nuevaPrioridad) Nota: el mtodo sleep() y join() tienen versiones sobrecargadas que no hemos mencionado.

Hilo Durmiendo (sleeping)

El mtodo sleep() es un mtodo esttico de la clase Thread. Generalmente lo usamos en el cdigo para pausar un poco la ejecucin de un proceso en particular forzndolo a dormir durante un tiempo determinado. Para forzar un hilo a dormir podemos usar un cdigo parecido a lo siguiente: try{ Thread.sleep(5*60*1000); //Duerme durante 5 minutos }catch(InterruptedException ex){}

Normalmente cuando llamamos al mtodo sleep() encerramos el cdigo en un bloque try/catch debido a que dicho mtodo arroja una excepcin.

El hecho de que un hilo deje de dormir, no significa que volver a estar ejecutndose al momento de despertar, el tiempo especificado dentro del mtodo sleep() es el mnimo de tiempo que un hilo debe de dormir, aunque puede ser mayor. De igual manera se debe de tomar en cuenta que el mtodo sleep() es esttico, es decir, solo hay uno para toda la clase Thread, por lo tanto, un hilo no puede poner a dormir a otro, el mtodo sleep() siempre afecta al hilo que se encuentra ejecutando al momento de hacer la llamada.

Prioridades de hilo y el mtodo yield()

Para entender el mtodo yield(), debemos entender las prioridades de los hilos. Un hilo siempre corre con una prioridad, normalmente va de 1 a 10, debido a esto, el programador de hilos generalmente utiliza su programacin basada en prioridades. Un hilo de menor prioridad que se encuentra ejecutando usualmente ser desplazado al estado en ejecucin para que un hilo de mayor prioridad pueda ejecutarse. De cualquier manera, nunca debes de basar el funcionamiento de tu aplicacin basado en las prioridades de los hilos. Para establecer la prioridad de un hilo debemos hacer algo parecido a lo siguiente:

HiloRunnable h = new HiloRunnable(); Thread t = new Thread(h); t.setPriority(7); t.start();

Algunas mquinas virtuales no son capaces de reconocer 10 valores diferentes de prioridad, lo que causa un problema al momento de la ejecucin, debido a ello, la clase Thread tiene 3 constantes (variables estticas y finales) que definen un rango de prioridades de hilo:

Thread.MIN_PRIORITY (1) Thread.NORM_PRIORITY (5) Thread.MAX_PRIORITY (10)

El mtodo yield() tiene la funcin de hacer que un hilo que se est ejecutando de regreso al estado en ejecucin(runnable) para permitir que otros hilos de la misma prioridad puedan ejecutarse. Sin embargo, el funcionamiento de este mtodo (al igual que de los hilos en general) no est garantizado, puede que despus de que se establezca un hilo por medio del mtodo yield() a su estado en ejecucin(runnable), ste vuelva a ser elegido para ejecutarse. El mtodo yield() nunca causar que un hilo pase a estado de espera/bloqueado/dormido, simplemente pasa de ejecutndose(running) a en ejecucin(runnable).

El mtodo no esttico join() permite al hilo formarse en la cola de espera de otro hilo. Si tienes un hilo B que no puede comenzar a ejecutarse hasta que se complete el proceso del hilo A, entonces querrs que B se forme en la cola de espera de A. Esto significa que B nunca podr ejecutarse si A no completa su proceso. En cdigo se utiliza as: Thread t = new Thread(); t.start(); t.join();

Existe una versin sobrecargada de join() que puede incluir el tiempo en milisegundos, por ejemplo si llamamos a t.join(5000), significa que el hilo que queremos ejecutar debe esperar a que el hilo que se est ejecutando en este momento termine, pero si tarda ms de 5 segundos entonces debe dejar de esperar y entrar en estado de ejecucin(runnable).

Aplicacin: Imaginemos que tenemos 2 hilos que estn accediendo a la misma instancia de una clase, ejecutando el mismo mtodo y accediendo incluso al mismo objeto y mismas variables, cada uno cambiando el estado primario de dicho objeto prcticamente de manera simultnea, lo mismo sucede con las variables. Si los datos que se estn modificando indican al programa cmo funcionar (y normalmente as es, si no para qu los queremos?) el pensar en esta situacin originara un resultado desastroso. class CuentaBanco { private int balance = 50;

public int getBalance() { return balance; }

public void retiroBancario(int retiro) { balance = balance retiro; } }

public class PeligroCuenta implements Runnable {

private CuentaBanco cb = new CuentaBanco();

public void run() {

for(int x = 0; x <> hacerRetiro(10); if(cb.getBalance()<0) System.out.println(La cuenta est sobregirada.); }

private void hacerRetiro(int cantidad) { if(cb.getBalance()>=cantidad) { System.out.println(Thread.currentThread().getName()+ va a hacer un retiro.); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); }

cb.retiroBancario(cantidad);

} else {

System.out.println(No ha suficiente dinero en la cuenta para realizar el retiro Sr. + Thread.currentThread().getName());

try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } } }

public static void main (String[] args) { PeligroCuenta pl = new PeligroCuenta(); Thread uno = new Thread(pl); Thread dos = new Thread(pl); uno.setName(Luis); dos.setName(Manuel);

uno.start(); dos.start(); } }

Si realizamos lo anterior, nos aparece un resultado parecido a lo siguiente:

Luis va a hacer un retiro. Manuel va a hacer un retiro. Manuel realiz el retiro con xito. Luis realiz el retiro con xito.

Luis va a hacer un retiro. Manuel va a hacer un retiro. Manuel realiz el retiro con xito. Manuel va a hacer un retiro. Luis realiz el retiro con xito. Luis va a hacer un retiro. Luis realiz el retiro con xito. No ha suficiente dinero en la cuenta para realizar el retiro Sr.Luis su saldo actual es de -10 Manuel realiz el retiro con xito. No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de -10 No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de -10 No ha suficiente dinero en la cuenta para realizar el retiro Sr.Luis su saldo actual es de -10 La cuenta est sobregirada. La cuenta est sobregirada.

Desastroso no?, mientras Luis estaba checando el estado de cuenta y vi que era posible el retiro, Manuel estaba retirando y viceversa, finalmente, Manuel verific que haba 10 pesos en el saldo y decidi retirarlos, pero oh sorpresa! Luis los acababa de retirar, sin embargo el retiro de Manuel tambin se complet dejando la cuenta sobregirada. A dicho escenario se le llama condicin de carrera, cuando 2 o ms procesos pueden acceder a las mismas variables y objetos al mismo tiempo y los datos pueden corromperse si un proceso corre lo suficientemente rpido como para vencer al otro.

Qu es lo que se hace en estas situaciones?. La respuesta es muy sencilla, lo nico que tenemos que hacer es checar el estado de cuenta y realizar el retiro en un solo proceso, sin separarlos. No se puede garantizar que durante esta operacin atmica, o dicho de otra manera, inseparable, el hilo que se est ejecutando permanecer en dicho estado hasta que se complete, lo que s podemos garantizar es que ningn otro hilo accede a los mismos datos hasta que el primero termine de realizar la operacin. Entonces qu hacemos para proteger los datos? Dos cosas:

- Marcar las variables como privadas. - Sincronizar el cdigo que modifica las variables.

Para marcar las variables como privadas utilizamos los identificadores de control de acceso, en este caso la palabra private. Para sincronizar el cdigo utilizamos la palabra synchronized. Dicho con cdigo private synchronized void hacerRetiro(int cantidad) { if(cb.getBalance()>=cantidad) { System.out.println(Thread.currentThread().getName()+ va a hacer un retiro.); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); }

cb.retiroBancario(cantidad); System.out.println(Thread.currentThread().getName() + realiz el retiro con xito.); }else{ System.out.println(No ha suficiente dinero en la cuenta para realizar el retiro Sr. + Thread.currentThread().getName()); System.out.println(su saldo actual es de +cb.getBalance());

try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } } }

Retomemos lo anterior. El mtodo que realiza las operaciones con las variables es hacerRetiro(), por lo tanto, es el mtodo que necesitamos que sea privado y sincronizado. Todo lo dems permanece igual. Si lo ejecutamos, obtenemos algo parecido a lo siguiente: Luis va a hacer un retiro. Luis realiz el retiro con xito. Luis va a hacer un retiro. Luis realiz el retiro con xito. Luis va a hacer un retiro. Luis realiz el retiro con xito. Luis va a hacer un retiro. Luis realiz el retiro con xito. Manuel va a hacer un retiro. Manuel realiz el retiro con xito. No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de 0 No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de 0 No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel

su saldo actual es de 0 No ha suficiente dinero en la cuenta para realizar el retiro Sr.Luis su saldo actual es de 0 No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de 0

La cuenta no se sobregir y seguramente nunca lo har. Verifca que ambas personas siempre checaron el saldo y realizaron el retiro antes de que la otra pudiera checar el saldo.

Ejemplos completos

Puedes descargar el empaquetado de programas Demos_de_hilos_Java en donde vienen varios demos bsico sobre hilos en Java como el siguiente:

/*

http://download.oracle.com/javase/1.5.0/docs/api/index.html?overview-summary.html

*/

public class Demo1 implements Runnable { private Thread hilo;

public Demo1() {

hilo = new Thread(this); hilo.start(); }

public static void main(String[] args) { /*Inicia el programa, se crea una instancia de Demo*/ new Demo1(); }

public void run() { /* este metodo es obligatorio, y es invocado en automtico al llamar start() */ //while (true) { /*Espera de 5 segundos*/ System.out.println (Llamada antes de la espera); try {Thread.sleep(5000); } catch (Exception e) {} System.out.println (Llamada despues de la espera); //}

/* Habilitando el while, se hace infinito el programa, se detiene con CTRL+C */ } }

Potrebbero piacerti anche