Está en la página 1de 29

Programacin Orientada a Objetos Trabajo Terico Mayo, 2004

Multihilo en Java
Ral Herrero Pascual Rubn Montero Diez

Departamento de Informtica y Automtica Universidad de Salamanca

Informacin de los autores:

Ral Herrero Pascual Departamento de Informtica y Automtica Facultad de Ciencias - Universidad de Salamanca Plaza de la Merced S/N 37008 - Salamanca ruly2oo2@yahoo.es Rubn Montero Diez Departamento de Informtica y Automtica Facultad de Ciencias - Universidad de Salamanca Plaza de la Merced S/N 37008 - Salamanca r_montero_diez@yahoo.es

Este documento puede ser libremente distribuido. Programacin Orientada a Objetos 2004

Resumen
En este documento se recogen los detalles fundamentales del manejo de threads en Java. No pretende ser ms que una introduccin a este tema en concreto, tratando simplemente de comentar las operaciones que se pueden realizar sobre ellos y explicar algunos detalles de su implementacin. Estas operaciones nos determinarn los diferentes estados del ciclo de vida por el que pasa un thread, tambin se explican algunas formas de sincronizacin para evitar problemas de concurrencia, y por ltimo las prioridades de los hilos para que estos se ejecuten en un determinado orden.

Abstract
In this document the basic details for working with threads in Java are presented. It only intent to be an introduction to this specific subject, we only try to comment the operations that we can realize with them and explain some details of them implementation. This operations decides the different states of the lifes cycle that a thread does, some ways of synchronization for preventing turnout problems are also explain, and finally threads priorities for that threads are executed in a fixed order.

Programacin Orientada a Objetos - 2004

Tabla de Contenidos 1. 2.
2.1. 2.2.

Introduccin ______________________________________________ Multitarea y multihilo ____________________________________

1 1

Programas de flujo nico ____________________________________ 2 Programas de flujo mltiple __________________________________ 2

3.
3.1. 3.2.

Creacin de threads _____________________________________

Construccin de una clase derivada de Thread ________________ 3 Implementacin del Interface Runnable _______________________ 4

4. 5.
5.1. 5.2. 5.3. 5.4.

Paso de parmetros a los threads ____________________ Ciclo de vida de un thread _____________________________

6 7

La clase Thread _____________________________________________ 7 Mtodos de Clase ___________________________________________ 7 Mtodos de Instancia ________________________________________ 7 Estados de un Thread _______________________________________ 9

6.
6.1.

Sincronizacin

__________________________________________ 11

Ejemplo____________________________________________________ 14

7.
7.1. 7.2.

Prioridades y threads daemon _______________________

19

Conmutacin de contexto ___________________________________ 19 Threads daemon ___________________________________________ 20

8. 9.

Grupos de hilos _________________________________________ Comentario_______________________________________________

20 21 22 23 23

10. Conclusiones ____________________________________________ 11. Referencias ______________________________________________ 12. Bibliografa _______________________________________________

II

Programacin Orientada a Objetos - 2004

Tabla de Ilustraciones
Figura 1. Disputa de CPU entre procesos pesados ____________________________ 2 Figura 2. Visualizacin de la salida de ThredEjemplo _________________________ 4 Figura 3. Estados de un thread ___________________________________________ 9 Figura 4. Situacin de los filsofos comilones_______________________________ 14 Figura 5. Visualizacin de la salida del problema de los filsofos comilones ______ 18

Programacin Orientada a Objetos - 2004

III

1. Introduccin
Actualmente los procesadores y los sistemas operativos permiten la multitarea, es decir, la realizacin simultnea de dos o ms actividades, al menos aparentemente. En la realidad, un ordenador con una sola CPU no puede realizar dos actividades a la vez. Sin embargo los sistemas operativos modernos son capaces de ejecutar varios programas "simultneamente" aunque slo dispongan de una CPU, para ello reparten el tiempo entre dos o ms actividades, o bien utilizan los tiempos muertos de una actividad, como por ejemplo en las operaciones de lectura de datos desde el teclado, para trabajar en la otra. Mientras que en ordenadores con dos o ms procesadores la multitarea es real, ya que cada procesador puede ejecutar un hilo o thread diferente. Un proceso es un programa ejecutndose de forma independiente y con un espacio propio de memoria, un sistema operativo multitarea es capaz de ejecutar ms de un proceso simultneamente. Un thread o hilo es un flujo secuencial simple dentro de un proceso, un nico proceso puede tener varios hilos ejecutndose. Por ejemplo el programa Netscape sera un proceso, mientras que cada una de las ventanas que se pueden tener abiertas simultneamente trayendo pginas HTML estara formada por al menos un hilo. Sin el uso de threads hay tareas que son prcticamente imposibles de ejecutar, en particular las que tienen tiempos de espera importantes entre etapas. Los threads o hilos de ejecucin permiten organizar los recursos del ordenador de forma que pueda haber varios programas actuando en paralelo. Un hilo de ejecucin puede realizar cualquier tarea que pueda realizar un programa normal y corriente. Bastar con indicar lo que tiene que hacer en el mtodo run(), que es el que define la actividad principal de las threads.

2. Multitarea y multihilo
Muchos entornos tienen la llamada multitarea en su sistema operativo, esto es distinto al multihilo. En un sistema operativo multitarea a las tareas se les llama procesos pesados, mientras que en un entorno multihilo se les denomina procesos ligeros o hilos, la diferencia es que los procesos pesados estn en espacios de direccionamiento distintos y por ello la comunicacin entre procesos y el cambio de contexto es caro. Por el contrario, los hilos comparten el mismo espacio de direcciones y comparten cooperativamente el mismo proceso pesado, cada hilo guarda su propia pila, variables locales y contador de programa, por ello, la comunicacin entre hilos es muy ligera y la conmutacin de contexto muy rpida. En la Figura 1 observamos cmo sobre una CPU pueden estar ejecutndose distintos procesos pesados y uno de ellos puede estar compuesto por distintos flujos de ejecucin (threads o hebras o hilos). Estos threads tendrn que disputarse el tiempo de ejecucin que el sistema operativo le d al proceso en el que residen.

-1-

Multihilo Java

Figura 1. Disputa de CPU entre procesos pesados

2.1.

Programas de flujo nico

Un programa de flujo nico o single-threaded utiliza un nico flujo de control para controlar su ejecucin. Muchos programas no necesitan la potencia o utilidad de mltiples flujos de control, sin necesidad de especificar explcitamente que se quiere un nico flujo de control. Por ejemplo: public class HolaMundo { static public void main( String args[] ) { System.out.println( "Hola Mundo" ); } } Aqu, cuando se llama a main(), la aplicacin imprime el mensaje y termina. Esto ocurre dentro de un nico thread, un nico hilo de ejecucin.

2.2.

Programas de flujo mltiple

En el ejemplo anterior no vemos el thread que ejecuta nuestro programa, se crea y se ejecuta de forma implcita, sin embargo Java posibilita la creacin y control de threads explcitamente. La utilizacin de threads en Java permite una enorme flexibilidad a los programadores a la hora de plantearse el desarrollo de aplicaciones. La simplicidad para crear, configurar y ejecutar threads, permite que se puedan implementar aplicaciones poderosas y portables que no se puede con otros lenguajes de tercera generacin. Esta herramienta es fundamental en un lenguaje con marcada orientacin a Internet como es Java. Las aplicaciones multithreaded utilizan muchos contextos de ejecucin para cumplir su trabajo, utilizan el hecho de que muchas tareas contienen subtareas distintas e independientes, pudiendo utilizar un thread para cada subtarea. Mientras que los programas de flujo nico pueden realizar su tarea ejecutando las subtareas secuencialmente, un programa multithreaded permite que cada thread comience y termine tan pronto como sea posible. Este comportamiento presenta una mejor respuesta a la entrada en tiempo real.

Programacin Orientada a Objetos - 2004

Herrero y Montero

3. Creacin de threads
Lo primero que hay que realizar para poder utilizar un thread es crearlo. El programador puede elegir cualquiera de las dos posibilidades que proporciona Java, y que como explica Pedro Manuel Cuenca en su libro [1], se describen a continuacin.

3.1.

Construccin de una clase derivada de Thread

La funcionalidad bsica de un thread esta implementada en la clase Thread. Cualquier clase que derive de esta puede construir un hilo de ejecucin independiente, para lo cual basta con que sobrescriba el mtodo run(). En este mtodo hay que incluir el cdigo que deseamos que se ejecute en paralelo con otras tareas. Para que comience la ejecucin del thread, debe invocarse el mtodo start(), que despus de realizar los preparativos convenientes invoca a su vez a run(). El siguiente ejemplo demuestra la creacin de un thread del modo descrito: public class ThreadEjemplo extends Thread { public ThreadEjemplo(String str) { super(str); } public void run() { for (int i = 0; i < 10 ; i++) System.out.println(i + " " + getName()); System.out.println("Termina thread " + getName()); } public static void main (String [] args) { new ThreadEjemplo("Pepe").start(); new ThreadEjemplo("Juan").start(); System.out.println("Termina thread main"); } } Notas sobre el programa: El constructor public Thread(String str) recibe un parmetro que es la identificacin del thread. El mtodo run() contiene el bloque de ejecucin del thread. Dentro de l, el mtodo getName() devuelve el nombre del thread, es decir, el que se ha pasado como argumento al constructor. El mtodo main() crea dos objetos de clase ThreadEjemplo y los inicia con la llamada al mtodo start(), dicho mtodo despus de iniciar el nuevo thread llama al mtodo run(). En la salida el primer mensaje que se visualizar ser el de finalizacin del thread main. La ejecucin de los threads es asncrona. Realiza la llamada al mtodo start(), ste le devuelve el control y continua su ejecucin independientemente de los otros threads. En la salida los mensajes de un thread y otro se van mezclando ya que la mquina virtual asigna tiempos a cada thread.

Programacin Orientada a Objetos - 2004

Multihilo Java

En la Figura 2 se puede observar una de las posibles salidas por pantalla que se pueden obtener al ejecutar este programa.

Figura 2. Visualizacin de la salida de ThredEjemplo

3.2.

Implementacin del Interface Runnable

Construir una clase derivada de Thread presenta el gran inconveniente de que para poder crear un thread es necesario que la clase que contiene el cdigo que se desea ejecutar en paralelo derive de Thread. Esta limitacin es muy importante ya que Java no dispone de herencia mltiple y por ello no podremos crear un thread con cualquier clase arbitraria que herede su funcionalidad de otras que sean de nuestro inters. Para solventar este problema, puede utilizarse un segundo mecanismo para la creacin de un thread. Este mecanismo consiste en que la clase de nuestro inters implemente el interface Runnable, y a continuacin crearamos un nuevo thread utilizando uno de los constructores de la clase Thread, que admite como argumento cualquier objeto que implemente Runnable. Este tcnica no supone ninguna restriccin, pues una clase puede implementar cualquier nmero de interfaces. El interface Runnable nicamente define un mtodo denominado run() y al igual que suceda en el caso anterior, debemos colocar dentro del mismo el cdigo que deseamos que se ejecute en paralelo con otros threads.

Programacin Orientada a Objetos - 2004

Herrero y Montero

El siguiente ejemplo es equivalente al del apartado anterior, pero utilizando la interface Runnable: public class ThreadEjemplo implements Runnable { public void run() { for (int i = 0; i < 5 ; i++) System.out.println(i + " " + Thread.currentThread().getName()); System.out.println("Termina thread " + Thread.currentThread().getName()); } public static void main (String [] args) { new Thread ( new ThreadEjemplo() , "Pepe").start(); new Thread ( new ThreadEjemplo() , "Juan").start(); System.out.println("Termina thread main"); } } Notas sobre el programa: Se implanta la interface Runnable en lugar de extender la clase Thread. El constructor que haba antes no es necesario. En el main() se puede observar la forma en que se crea el thread, esa expresin es equivalente a: ThreadEjemplo ejemplo = new ThreadEjemplo(); Thread thread = new Thread ( ejemplo , "Pepe"); thread.start(); Primero se crea la instancia de nuestra clase. Despus se crea una instancia de la clase Thread, pasando como parmetros la referencia de nuestro objeto y el nombre del nuevo thread. o Por ltimo se llama al mtodo start() de la clase Thread. Este mtodo iniciar el nuevo thread y llamar al mtodo run() de nuestra clase. Por ltimo, obsrvese la llamada al mtodo getName() desde run(). getName() es un mtodo de la clase Thread, por lo que nuestra clase debe obtener una referencia al thread propio. Es lo que hace el mtodo esttico currentThread() de la clase Thread. o o

Programacin Orientada a Objetos - 2004

Multihilo Java

4. Paso de parmetros a los threads


El mtodo run() no toma argumentos, sin embargo, en muchas ocasiones es necesario pasarle parmetros a un thread. Esto se puede conseguir encapsulando el thread en una clase y pasando los parmetros al constructor de la misma para almacenarlos en variables locales. En el siguiente ejemplo consideramos una aplicacin que crea un nuevo esclavo en un thread por cada nuevo trabajo que le llega.

class Parametros implements Runnable { private int parm1; private int parm2; private Thread me; public Parametros (int p1, int p2, String nombre) { parm1 = p1; parm2 = p2; me = new Thread (this,nombre); me.start(); } public void run() { if (me == Thread.currentThread()) { System.out.println(Thread.currentThread().getName() +"\n\tp1 = "+ parm1 + "\n\tp2 = " + parm2); } } public static void main (String[] args) { new Parametros(4,5,"Hilo 1"); new Parametros(7,9,"Hilo 2"); } } Esto imprimira: Hilo 1 p1 = 4 p2 = 5 Hilo 2 p1 = 7 p2 = 9

Programacin Orientada a Objetos - 2004

Herrero y Montero

5. Ciclo de vida de un thread


5.1. La clase Thread

Es la clase que encapsula todo el control necesario sobre los hilos de ejecucin (threads). Hay que distinguir claramente un objeto Thread de un hilo de ejecucin o thread. Esta distincin resulta complicada, aunque se puede simplificar si se considera al objeto Thread como el panel de control de un hilo de ejecucin. La clase Thread es la nica forma de controlar el comportamiento de los hilos y para ello se sirve de los mtodos que se exponen a continuacin.

5.2.

Mtodos de Clase

Estos son los mtodos estticos que deben llamarse de manera directa en la clase Thread. currentThread(): este mtodo devuelve el thread que se est ejecutando en este momento, es decir, aqul desde donde se produjo la invocacin a currentThread(). yield(): este mtodo libera el procesador para que pueda ser utilizado por otros threads. Esto no significa que el thread se detenga definitivamente, sino que simplemente indica al planificador que pase a ejecutar otro thread. El thread que invoc a yield() se volver a ejecutar cuando le toque el turno de nuevo. sleep(long): este mtodo interrumpe la ejecucin del hilo en curso durante el nmero de milisegundos que se indiquen en el parmetro de tipo long. Una vez transcurridos esos milisegundos, dicho hilo volver a estar disponible para su ejecucin. activeCount(): este mtodo devuelve el nmero de threads activos dentro del grupo donde est incluido el thread en ejecucin.

5.3.

Mtodos de Instancia
start(), stop(), run(), destroy(): estos mtodos son los que controlan el funcionamiento bsico del thread. El mtodo start() se invoca cuando se desea comenzar la ejecucin del thread. La implementacin de la clase Thread realiza diversas tareas de iniciacin antes de pasar a invocar el mtodo run(). El programador puede utilizar start() para aadir cdigo adicional de inicializacin, aunque esto slo es posible si se crea una subclase de Thread. Como puede imaginarse, stop() sirve para indicar al thread que debe terminar su ejecucin, mientras que destroy() tiene como cometido liberar todos los recursos que pudieran estar emplendose. getName(), setName(): los threads pueden tener asociado un nombre, lo que facilita las labores de depuracin o incluso la creacin de trazas en el cdigo para supervisar la ejecucin, una de las formas para asignar este nombre es mediante el mtodo setName(). En cuanto a la depuracin cabe destacar que la realizacin de

Programacin Orientada a Objetos - 2004

Multihilo Java

programas con varios threads es considerablemente ms compleja y difcil de depurar que el desarrollo de aplicaciones sin esta capacidad. Adems del mtodo setName(), existen constructores de la clase Thread en los que se pueden especificar el nombre que se desea asignar. getPriority(), setPriotity(): puede asignarse una prioridad a cada thread, de modo que se asigne ms tiempo de ejecucin a los threads ms prioritarios. Esto es todo lo que Java permite profundizar sobre el estudio de la prioridad, siendo imposible conocer cmo se realizar la planificacin entre threads de igual prioridad. La prioridad de un thread ha de estar comprendida entre los valores MIN_PRIORITY y MAX_PRIORITY. Cuando se crea un thread, recibe por defecto la prioridad NORM_PRIORITY. getThreadGroup(): este mtodo obtiene el grupo donde est contenido el thread. No existe un mtodo setThreadGroup(), por consiguiente, debe emplearse alguna versin del constructor para indicar el grupo a donde se desea que pertenezca el thread. suspend(): el mtodo suspend() es distinto de stop(). suspend() toma el hilo y provoca que se detenga su ejecucin sin destruir el hilo. Si la ejecucin de un hilo se suspende, puede llamarse a resume() sobre el mismo hilo para lograr que vuelva a ejecutarse de nuevo. resume(): el mtodo resume() se utiliza para revivir un hilo suspendido. No hay garantas de que el hilo comience a ejecutarse inmediatamente, ya que puede haber un hilo de mayor prioridad en ejecucin actualmente, pero resume() ocasiona que el hilo vuelva a ser un candidato a ser ejecutado. isAlive(): el interfaz de programacin de la clase Thread incluye el mtodo isAlive(), que devuelve true si el hilo ha sido arrancado (con start()) y no ha sido detenido (con stop()). Por ello, si el mtodo isAlive() devuelve false; sabemos que estamos ante un Nuevo thread o ante un thread Muerto, y si devuelve true; se sabe que el hilo se encuentra en estado Ejecutable o Parado, todos estos estados se explican en el siguiente apartado. No se puede diferenciar entre Nuevo thread y Muerto, ni entre un hilo Ejecutable o Parado. join(): este mtodo detiene el hilo actual hasta que termine el hilo sobre el que se llama join(). Es usado por tanto para que unos hilos esperen a la finalizacin de otros. Se acaban de explicar los mtodos de clase y mtodos de instancia ms interesantes dentro del mbito de este trabajo. Pero existen muchos ms y se pueden consultar en la pgina web de Sun [2].

Programacin Orientada a Objetos - 2004

Herrero y Montero

5.4.

Estados de un Thread

Durante el ciclo de vida de un hilo, ste se puede encontrar en diferentes estados. En la Figura 3 se muestran los estados y algunos mtodos que provocan el paso de un estado a otro.

Figura 3. Estados de un thread

5.4.1

Nuevo thread

Las siguientes sentencias crean un nuevo hilo de ejecucin pero no lo arrancan, lo dejan en el estado de Nuevo thread: Thread MiThread = new MiClaseThread("hiloA"); Thread MiThread = new Thread( new UnaClaseThread,"hiloA"); Cuando un hilo est en este estado, es simplemente un objeto Thread vaco. El sistema no ha destinado ningn recurso para l, nicamente memoria. Desde este estado solamente puede arrancarse llamando al mtodo start(), o detenerse definitivamente, llamando al mtodo stop(). La llamada a cualquier otro mtodo carece de sentido y lo nico que provocar ser la generacin de una excepcin de tipo IllegalThreadStateException.

5.4.2

Ejecutable

Obsrvense las dos lneas de cdigo siguientes: Thread MiThread = new MiClaseThread(); MiThread.start(); La llamada al mtodo start() reservar los recursos del sistema necesarios para que el hilo pueda ejecutarse, lo incorpora a la lista de procesos disponibles para ejecucin del sistema y llama al mtodo run() del hilo de ejecucin. En este momento se encuentra en el estado

Programacin Orientada a Objetos - 2004

Multihilo Java

Ejecutable del diagrama. Y este estado es Ejecutable y no En Ejecucin, porque cuando el hilo est aqu no significa que est corriendo. Muchos ordenadores tienen solamente un procesador lo que hace imposible que todos los hilos estn corriendo al mismo tiempo. Java implementa un tipo de scheduling o lista de procesos, que permite que el procesador sea compartido entre todos los procesos o hilos que se encuentran en la lista. Cuando el hilo se encuentra en este estado, todas las instrucciones de cdigo que se encuentren dentro del bloque declarado para el mtodo run(), se ejecutarn secuencialmente.

5.4.3

Parado

Un hilo de ejecucin entra en estado Parado cuando alguien llama al mtodo suspend(), cuando se llama al mtodo sleep(), cuando el hilo est bloqueado en un proceso de entrada/salida o cuando el hilo utiliza su mtodo wait() para esperar a que se cumpla una determinada condicin. Por ejemplo, en el trozo de cdigo siguiente: Thread MiThread = new MiClaseThread(); MiThread.start(); try { MiThread.sleep( 10000 ); } catch( InterruptedException e ) {} la lnea de cdigo que llama al mtodo sleep() hace que el hilo se duerma durante 10 segundos. Durante ese tiempo, incluso aunque el procesador estuviese totalmente libre, MiThread no correra. Despus de esos 10 segundos MiThread volvera a estar en estado Ejecutable y ahora s que el procesador podra hacerle caso cuando se encuentre disponible. Los mtodos de recuperacin del estado Ejecutable, en funcin de la forma de llegar al estado Parado del hilo, son los siguientes: Si un hilo est dormido, pasado el tiempo especificado en el sleep(). Si un hilo de ejecucin est suspendido, despus de una llamada a su mtodo resume(). Si un hilo est bloqueado en una entrada/salida, una vez que el comando de entrada/salida concluya su ejecucin. Si un hilo est esperando por una condicin, cada vez que la variable que controla esa condicin vare debe llamarse al mtodo notify() o notifyAll() .

5.4.4

Muerto

Un hilo de ejecucin se puede morir de dos formas: por causas naturales o porque lo maten (con stop()). Un hilo muere normalmente cuando concluye su mtodo run(). Por ejemplo, en el siguiente trozo de cdigo: public void run() { int i=0; while( i < 20 ) { i++; System.out.println( "i = " + i ); } }

Programacin Orientada a Objetos - 2004

10

Herrero y Montero

un hilo morir de forma natural despus de que se complete el bucle y run() concluya. Tambin se puede matar en cualquier momento un hilo, invocando a su mtodo stop(), en el trozo de cdigo siguiente: Thread MiThread = new MiClaseThread(); MiThread.start(); try { MiThread.sleep( 10000 ); } catch( InterruptedException e ) {} MiThread.stop(); se crea y arranca el hilo MiThread, se duerme durante 10 segundos y en el momento de despertarse, la llamada al mtodo stop() lo mata. El mtodo stop() enva un objeto ThreadDeath al hilo de ejecucin que quiere detener. As, cuando un hilo es parado de este modo, muere asncronamente. El hilo morir en el momento en que reciba ese objeto ThreadDeath. Los applets utilizarn el mtodo stop() para matar a todos sus hilos cuando el navegador con soporte Java en el que se estn ejecutando le indica al applet que se detengan, por ejemplo, cuando se minimiza la ventana del navegador o cuando se cambia de pgina.

6. Sincronizacin
La sincronizacin surge debido a la necesidad de evitar que dos o ms threads traten de acceder a los mismos recursos al mismo tiempo. Por ejemplo si un thread tratara de escribir en un fichero y otro thread estuviera al mismo tiempo tratando de borrar dicho fichero, se producira una situacin no deseada. Tambin habra que sincronizar hilos cuando un thread debe esperar a que estn preparados los datos que le debe suministrar otro thread. Para solucionar estos tipos de problemas es importante poder sincronizar los distintos threads. Las secciones de cdigo de un programa que acceden a un mismo recurso (un mismo objeto de una clase, un fichero del disco, etc.) desde dos threads distintos se denominan secciones crticas. Para sincronizar dos o ms threads, hay que utilizar el modificador synchronized en aquellos mtodos del objeto o recurso con los que puedan producirse situaciones conflictivas. De esta forma Java bloquea, asocia un bloqueo o lock, el recurso sincronizado. Por ejemplo: public synchronized void metodoSincronizado() { ...// cdigo de la seccin critica } La sincronizacin previene las interferencias solamente sobre un tipo de recurso: la memoria reservada para un objeto. Cuando se prevea que unas determinadas variables de una clase pueden tener problemas de sincronizacin, se debern declarar como private o protected. As solamente sern accesibles a travs de mtodos de la clase, los cuales debern estar sincronizados. Es muy importante tener en cuenta que si se sincronizan algunos mtodos de un objeto pero otros no, el programa puede que no funcione correctamente. La razn es que los mtodos no sincronizados pueden acceder libremente a las variables miembro ignorando el bloqueo del objeto, slo los mtodos sincronizados comprueban si un objeto est bloqueado.

11

Programacin Orientada a Objetos - 2004

Multihilo Java

Todos los mtodos que accedan a un recurso compartido deben ser declarados synchronized. De esta forma, si algn mtodo accede a un determinado recurso Java bloquea dicho recurso, as logramos que el resto de threads no puedan acceder al mismo hasta que el primero en acceder termine de realizar su tarea. Bloquear un recurso u objeto significa que sobre ese objeto no pueden actuar simultneamente dos mtodos sincronizados. En la sincronizacin se pueden utilizar dos niveles de bloqueo de un recurso, el primero es a nivel de objetos, mientras que el segundo es a nivel de clases. El primero se consigue declarando todos los mtodos de una clase como synchronized. Cuando se ejecuta un mtodo synchronized sobre un objeto concreto, el sistema bloquea dicho objeto, de forma que si otro thread intenta ejecutar algn mtodo sincronizado de ese objeto, este segundo mtodo se mantendr a la espera hasta que finalice el anterior y desbloquee por lo tanto el objeto. Si existen varios objetos de una misma clase, como los bloqueos se producen a nivel de objeto, es posible tener distintos threads ejecutando mtodos sobre diversos objetos de una misma clase. El bloqueo de recursos a nivel de clases se corresponde con los mtodos de clase o static, y por lo tanto con las variables de clase o static. Si lo que se desea es conseguir que un mtodo bloquee simultneamente una clase entera, es decir, todos los objetos creados de una clase, ser necesario declarar este mtodo como synchronized static. Durante la ejecucin de un mtodo declarado de esta segunda forma ningn mtodo sincronizado tendr acceso a ningn objeto de la clase bloqueada. La sincronizacin puede ser problemtica y generar errores. Un thread podra bloquear un determinado recurso de forma indefinida impidiendo que el resto de threads accedieran al mismo, para evitar esto habr que utilizar la sincronizacin slo donde sea estrictamente necesario. Hay que tener presente que si dentro de un mtodo sincronizado se utiliza el mtodo sleep() de la clase Thread, el objeto bloqueado permanecer dormido durante el tiempo indicado en el argumento de dicho mtodo. Esto implica que otros threads no podrn acceder a ese objeto durante ese tiempo, aunque en realidad no exista peligro de simultaneidad ya que durante ese tiempo el thread que mantiene bloqueado el objeto no realizar cambios. Para evitarlo es conveniente sustituir sleep() por el mtodo wait(). Cuando se llama al mtodo wait(), que siempre debe hacerse desde un mtodo o bloque synchronized, se libera el bloqueo del objeto y por lo tanto es posible continuar utilizando ese objeto a travs de mtodos sincronizados. El mtodo wait() detiene el thread hasta que se llame al mtodo notify() o notifyAll() del objeto, o finalice el tiempo indicado como argumento del mtodo wait(). El mtodo unObjeto.notify() lanza una seal indicando al sistema que puede activar uno de los threads que se encuentren bloqueados esperando para acceder al objeto unObjeto. El mtodo notifyAll() lanza una seal a todos los threads que estn esperando la liberacin del objeto. Los mtodos notify() y notifyAll() deben ser llamados desde el thread que tiene bloqueado el objeto para activar el resto de threads que estn esperando la liberacin de un objeto. Un thread se convierte en propietario del bloqueo de un objeto ejecutando un mtodo sincronizado del objeto. Observar las dos funciones siguientes, en las que put() inserta un dato y get() lo recoge:

Programacin Orientada a Objetos - 2004

12

Herrero y Montero

public synchronized int get() { while (available == false) { try { wait(); } catch (InterruptedException e) { } } available = false; notifyAll(); return contents; } public synchronized void put(int value) { while (available == true) { try { wait(); } catch (InterruptedException e) { } } contents = value; available = true; notifyAll(); } Notas sobre el programa: El bucle while de la funcin get() contina ejecutndose (avalaible == false) hasta que el mtodo put() haya suministrado un nuevo valor y lo indique con avalaible = true. En cada iteracin del while la funcin wait() hace que el hilo que ejecuta el mtodo get() se detenga hasta que se produzca un mensaje de que algo ha sido cambiado, en este caso con el mtodo notifAll() ejecutado por put(). El mtodo put() funciona de forma similar.

Tambin existe la posibilidad de sincronizar una parte del cdigo de un mtodo sin necesidad de mantener bloqueado el objeto desde el comienzo hasta el final del mtodo. Para ello se utiliza la palabra clave syncronized indicando entre parntesis el objeto que se desea sincronizar (synchronized(objetoASincronizar)). Por ejemplo si se desea sincronizar el propio thread en una parte del mtodo run(), el cdigo podra ser: public void run() { while(true) { ... syncronized(this) { // El objeto a sincronizar es el propio thread ... // Cdigo sincronizado } try { sleep(2000); // Se detiene el thread durante 2 segundos pero el objeto // es accesible por otros threads al no estar sincronizado } catch(InterruptedException e) {} } }

13

Programacin Orientada a Objetos - 2004

Multihilo Java

Un thread puede llamar a un mtodo sincronizado de un objeto para el cual ya posee el bloqueo, volviendo a adquirir el bloqueo. Por ejemplo: public class VolverAAdquirir { public synchronized void a() b(); System.out.println("Estoy } public synchronized void b() System.out.println("Estoy } } El anterior ejemplo obtendr como salida: Estoy en b() Estoy en a() debido a que se ha podido acceder al objeto con el mtodo b() al ser el thread que ejecuta el mtodo a() propietario con anterioridad del bloqueo del objeto. El proceso de sincronizacin lleva bastante tiempo a la CPU, por ello se debe minimizar su uso ya que el programa ser ms lento cuanta ms sincronizacin incorpore. { en a()"); { en b()");

6.1.

Ejemplo

EL PROBLEMA DE LOS FILSOFOS COMILONES. Cinco filsofos pasan su vida pensando y comiendo. Los filsofos comparten una mesa circular rodeada por cinco sillas, una para cada uno de ellos. En el centro de la mesa se encuentra una fuente de arroz, y tambin sobre la mesa hay cinco palillos chinos. Cuando un filsofo piensa, no interacta con sus colegas. Ocasionalmente, un filsofo tiene hambre y trata de coger los dos palillos que estn ms cerca de l (los palillos colocados entre l y sus vecinos de la derecha y de la izquierda). Un filsofo slo puede coger un palillo a la vez y, obviamente, no puede ser el que est en la mano de un vecino. Cuando un filsofo hambriento tiene sus dos palillos al mismo tiempo, come sin soltarlos. Cuando termina de comer, coloca ambos palillos sobre la mesa y comienza a pensar otra vez. La situacin de los Filsofos comelones se puede observar en la Figura 4.

Figura 4. Situacin de los filsofos comilones

Programacin Orientada a Objetos - 2004

14

Herrero y Montero

class semAforo { private int valor=1; synchronized void decrementar() { while(valor<=0) { try{ wait(); }catch(InterruptedException e) {} } valor--; } synchronized void incrementar() { valor++; notify(); } } class pensadores1 extends Thread { private int number; private semAforo palillo_izq; private semAforo palillo_der; long dormir; Random alea=new Random(1); public pensadores1(semAforo izq,semAforo der,int num) { palillo_izq=izq; palillo_der=der; this.number=num; } public void run() { for(;;) { palillo_izq.decrementar(); System.out.println("<"+this.number+"> Conseguido palillo izquierdo"); palillo_der.decrementar(); System.out.println("<"+this.number+"> Conseguido palillo derecho"); System.out.println("<"+this.number+"> Estoy comiendo"); try { dormir=(alea.nextInt(4))+10; sleep(dormir*1000);

15

Programacin Orientada a Objetos - 2004

Multihilo Java

} catch (InterruptedException e) {} System.out.println("<"+this.number+"> Estoy pensando"); palillo_izq.incrementar(); palillo_der.incrementar(); try { dormir=(alea.nextInt(4))+5; sleep(dormir*1000); } catch (InterruptedException e) {} } } } class pensadores2 extends Thread { private int number; private semAforo palillo_izq; private semAforo palillo_der; long dormir; Random alea=new Random(2); public pensadores2(semAforo izq,semAforo der,int num) { palillo_izq=izq; palillo_der=der; this.number=num; } public void run() { for(;;) { palillo_der.decrementar(); System.out.println("<"+this.number+"> Conseguido palillo derecho"); palillo_izq.decrementar(); System.out.println("<"+this.number+"> Conseguido palillo izquierdo"); System.out.println("<"+this.number+"> Estoy comiendo"); try { dormir=(alea.nextInt(4))+6; sleep(dormir*1000); } catch (InterruptedException e) {} System.out.println("<"+this.number+"> Estoy pensando"); palillo_izq.incrementar(); palillo_der.incrementar(); try { dormir=(alea.nextInt(4))+5;

Programacin Orientada a Objetos - 2004

16

Herrero y Montero

sleep(dormir*1000); } catch (InterruptedException e) {} } } } class probar { public static void main(String args[]) { semAforo palillo1=new semAforo(); semAforo palillo2=new semAforo(); semAforo palillo3=new semAforo(); semAforo palillo4=new semAforo(); semAforo palillo5=new semAforo(); pensadores1 pensadores2 pensadores1 pensadores2 pensadores1 try { p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p1.join(); p2.join(); p3.join(); p4.join(); p5.join(); }catch(java.lang.InterruptedException ie) { System.out.println(ie); } } } p1=new p2=new p3=new p4=new p5=new pensadores1(palillo1,palillo2,1); pensadores2(palillo2,palillo3,2); pensadores1(palillo3,palillo4,3); pensadores2(palillo4,palillo5,4); pensadores1(palillo5,palillo1,5);

17

Programacin Orientada a Objetos - 2004

Multihilo Java

En la Figura 5 se puede observar una de las posibles salidas por pantalla que se pueden obtener al ejecutar este programa.

Figura 5. Visualizacin de la salida del problema de los filsofos comilones

Notas sobre el programa: Tenemos dos clases de pensadores; pensadores1 y pensadores2, que se diferencian en que uno coge primero el palillo izquierdo y otro el palillo derecho. Tenemos adems otras dos clases; la clase semAforo y la clase probar que definir el mtodo main(). La clase semAforo tiene dos mtodos; incrementar() y decrementar() que nos sirven para controlar que los palillos slo sean utilizados por un solo filsofo, esto se consigue haciendo que esta operacin sea de forma atmica y as prevenir la concurrencia, para ello se declaran como synchronized y se usan los mtodos wait y notify. La clase pensadores1 extiende la clase Thread (la hereda) y por ello no es necesario implementar el Runnable. Esta clase adems del constructor, tiene un mtodo run() que va a determinar las acciones a realizar por cada hilo, es decir, por cada filsofo. Estos lucharn por conseguir el palillo de su izquierda, a continuacin el de la derecha, y posteriormente comern. Despus de comer, dormirn un tiempo aleatorio y los soltarn para que los dems los utilicen. La clase pensadores2 es exactamente igual que la clase pensadores1 con la salvedad de que el orden en coger los palillos es al contrario para evitar que se produzca un interbloqueo debido a que todos los filsofos cojan su palillo izquierdo y esperen por el derecho.

Programacin Orientada a Objetos - 2004

18

Herrero y Montero

La clase probar esta constituida por el mtodo principal, es decir, el mtodo main() que nos sirve para ejecutar el programa y utilizar las clases anteriormente definidas. En l se instancian varios objetos, de los cuales los de la clase pensadores1 y pensadores2 llamarn al mtodo start() para iniciar la ejecucin de dichos hilos (implcitamente se llamar tambin a run()) y se lleven a cabo las operaciones implementadas, con lo que conseguiremos solucionar el problema. Por ltimo esperamos por la finalizacin de los hilos mediante el mtodo join().

7. Prioridades y threads daemon


Para conseguir una correcta ejecucin de un programa se establecen prioridades en los threads, de forma que se produzca un reparto ms eficiente de los recursos disponibles. As, en un determinado momento, interesar que un determinado proceso acabe lo antes posible sus clculos, de forma que habr que otorgarle ms recursos, ms tiempo de CPU. Esto no significa que el resto de procesos no requieran tiempo de CPU, sino que necesitarn menos. La forma de llevar a cabo esto es gracias a las prioridades. Cuando se crea un nuevo thread, ste hereda la prioridad del thread desde el que ha sido inicializado. Las prioridades vienen definidas por variables miembro de la clase Thread, que toman valores enteros que oscilan entre la mxima prioridad MAX_PRIORITY (normalmente tiene el valor 10) y la mnima prioridad MIN_PRIORITY (valor 1), siendo la prioridad por defecto NORM_PRIORITY (valor 5). Para modificar la prioridad de un thread se utiliza el mtodo setPriority(). Se obtiene su valor con getPriority().

7.1.

Conmutacin de contexto

Las reglas para la conmutacin de contexto entre threads son sencillas. Un hilo puede ceder voluntariamente el control (por abandono explcito, al quedarse dormido o al bloquearse en espera de una E/S pendiente), o puede ser desalojado. En el primer caso, se examinan todos los dems restantes y se selecciona aquel que, estando listo para su ejecucin, tenga la prioridad ms alta, para su asignacin a la CPU. En el segundo caso, un hilo de baja prioridad que no libera la CPU es desalojado por otro de mayor prioridad, con independencia de lo que estuviese haciendo en ese instante. En otras palabras, tan pronto como un hilo de mayor prioridad desee comenzar su ejecucin, lo har. Esto se suele conocer como multitarea por desalojo. En caso de tareas con igual prioridad se suele aplicar algn algoritmo de round-robin para conmutar entre tareas. Un thread puede en un determinado momento renunciar a su tiempo de CPU y otorgrselo a otro thread de la misma prioridad, mediante el mtodo yield(), aunque en ningn caso a un thread de prioridad inferior.

19

Programacin Orientada a Objetos - 2004

Multihilo Java

7.2.

Threads daemon

Los threads pueden ser daemon o no daemon. Son daemon aquellos hilos que realizan en background (en un segundo plano) servicios generales, es decir, tareas que no forman parte de la esencia del programa y que se estn ejecutando mientras no finalice la aplicacin. Un thread daemon podra ser por ejemplo aqul que est comprobando permanentemente si el usuario pulsa un botn. Un programa de Java finaliza cuando slo quedan corriendo threads de tipo daemon. Por defecto, y si no se indica lo contrario, los threads son del tipo no daemon. Los thread Daemon tienen la prioridad ms baja. Se usa el mtodo setDaemon (true) para marcar un thread como thread demonio y se usa getDaemon para comprobar ese indicador. Por defecto, la cualidad de demonio se hereda desde el thread que crea el nuevo thread. No puede cambiarse despus de haber iniciado un thread.

8. Grupos de hilos
Cualquier hilo de ejecucin en Java debe formar parte de un grupo, la clase ThreadGroup define e implementa la capacidad de un grupo de hilos. Los grupos de hilos permiten que sea posible recoger varios hilos de ejecucin en un solo objeto y manipularlo como un grupo, en vez de individualmente. Por ejemplo, se pueden regenerar los hilos de un grupo mediante una sola sentencia. Cuando se crea un nuevo hilo, se coloca en un grupo, bien indicndolo explcitamente, o bien dejando que el sistema lo coloque en el grupo por defecto. Una vez creado el hilo y asignado a un grupo, ya no se podr cambiar a otro grupo. Si no se especifica un grupo en el constructor, el sistema coloca el hilo en el mismo grupo en que se encuentre el hilo de ejecucin que lo haya creado, y si no se especifica el grupo para ninguno de los hilos, entonces todos sern miembros del grupo main, que es creado por el sistema cuando arranca la aplicacin. La clase Thread proporciona constructores en los que se puede especificar el grupo del hilo que se esta creando en el mismo momento de instanciarlo, y tambin mtodos como getThreadGroup(), que permiten determinar el grupo en que se encuentra un hilo de ejecucin.

Programacin Orientada a Objetos - 2004

20

Herrero y Montero

9. Comentario
La posibilidad de soportar varios threads nos proporciona una gran flexibilidad para el diseo de aplicaciones. La especificacin de Java slo menciona que pueden utilizarse los recursos del sistema operativo subyacente si ste soporta multithreading, en caso contrario es la maquina virtual quien realiza todas las labores de planificacin y cambio de contexto. El comportamiento que puede esperar el programador del soporte de multithreading en Java no permite la realizacin de aplicaciones con fuertes requisitos temporales. No es posible tan siquiera cambiar la poltica de planificacin de la maquina virtual, y lo que es peor, sta depende de la plataforma en la que nos encontremos. Debido a estos motivos la utilizacin de multithreading en Java debe limitarse simplemente a situaciones donde sea conveniente tener varias tareas en paralelo, sin que importe demasiado la precisin con que se reparten el procesador o la frecuencia con que realizan cambios de contexto. Existe un aspecto significativo y no intuitivo dentro de los hilos, y es que debido a la planificacin de los hilos, se puede hacer que una aplicacin se ejecute generalmente ms rpido insertando llamadas a sleep() dentro del bucle principal de run(). Debido a esto su uso parece un arte, sobre todo cuando unos retrasos ms largos parecen incrementar el rendimiento. La razn por la que ocurre esto es que retrasos ms breves pueden hacer que la interrupcin del planificador del final de sleep() se d antes de que el hilo en ejecucin este listo para ir a dormir, forzando as al planificador a detenerlo y volver a arrancarlo ms tarde para que pueda acabar con lo que estaba haciendo, para ir despus a dormir. Las desventajas principales del multihilado son: 1. Ralentizacin durante la espera por recursos compartidos. 2. Sobrecarga adicional de la CPU necesaria para gestionar los hilos. 3. Complejidad sin recompensa, como la idea poco acertada de tener un hijo separado para actualizar cada elemento de un array. 4. Problemas derivados como la inanicin, la competicin y el interbloqueo.

21

Programacin Orientada a Objetos - 2004

Multihilo Java

10.

Conclusiones
La programacin de hilos requiere un cambio en la forma de pensar al programar, ya que la ejecucin del cdigo ya no se realiza de forma secuencial sino paralelamente. Tiene suma importancia saber cundo hay que hacer uso de threads y cundo evitarlos. Las principales razones de usarlos son al gestionar varias tareas que al entremezclarse hagan un uso ms eficiente del ordenador, o porque al usuario le interese por una determinada circunstancia. Un nico hilo es similar a un programa secuencial; es decir, tiene un comienzo, una secuencia y un final, adems en cualquier momento durante la ejecucin existe un slo punto de ejecucin. Sin embargo, un hilo no es un programa; no puede correr por s mismo, corre dentro de un programa. Un hilo por si mismo no nos ofrece nada nuevo, es la habilidad de ejecutar varios hilos dentro de un programa lo que ofrece algo nuevo y til, ya que cada uno de estos hilos puede ejecutar tareas distintas. Una ventaja de usar hilos es que las conmutaciones de contexto de procesos ligeras, sustituyen a las conmutaciones de contexto de procesos pesadas. Debido a que todos los hilos de un determinado proceso comparten el mismo espacio de memoria, una conmutacin del proceso ligera slo cambia la ejecucin del programa y las variables locales, mientras que una conmutacin de contexto pesada debe intercambiar todo el espacio en memoria. La mayor dificultad de trabajar con hilos es que dado un recurso, este podra estar siendo compartido por ms de un hilo. Por ello hay que asegurarse de que varios hilos no intenten leer y cambiar un recurso simultneamente. Esto requiere de un uso cuidadoso de synchronized, ya que aunque es una herramienta muy til, puede llevar a situaciones de interbloqueo sin darnos cuenta. Los programas paralelos son usados en ambientes crticos y muy sensibles tales como operaciones y manipulacin en bases de datos, recoleccin de informacin, etc. Con un creciente nfasis en el Web y herramientas independientes de la plataforma, es por esto que Java ha tenido un gran impacto en la creacin de nuevos sistemas paralelo. El problema es que Java soporta hilos, pero no ofrece una proteccin adecuada para los recursos compartidos por los hilos y no tiene un mecanismo de deteccin de interbloqueos. En Java parece que existe un lmite en la cantidad de hilos a crear, ya que en algunas ocasiones un nmero de hilos muy elevado da muestras de colapso, este punto crtico puede que se alcance con unos pocos cientos. Normalmente slo se crean unos pocos hilos para solucionar un problema y por tanto este lmite no se suele alcanzar, aunque puede parecer una limitacin en grandes diseos.

Programacin Orientada a Objetos - 2004

22

Herrero y Montero

11.

Referencias

[1] Pedro Manuel Cuenca Jimnez. Programacin en Java. Gua Prctica para programadores. Anaya Multimedia.
[2] API de Java. Web Site.

http://java.sun.com/j2se/1.4.2/docs/api/index.html [ltima vez visitado, 8-6-2004]

12.

Bibliografa
Ed Tittel, Bill Brogden. Manual Fundamental de Java. Anaya Multimedia. H. M. Deitel, P. J. Deitel . Cmo programar en Java. Prentice-Hall Hispanoamericana. 1a. Ed. Bruce Eckel; traduccin, Jorge Gonzlez Barturen. Piensa en Java . Prentice Hall. Roger Cadenhead. Java 2 in 24 hours. Sams Publising.

Pgina web con informacin acerca de varios lenguajes de programacin en general, y de Java en particular. http://www.programacion.net/java/ [ltima vez visitado, 8-6-2004] Pgina oficial de Sun dedicada al lenguaje Java.

http://java.sun.com/ [ltima vez visitado, 8-6-2004]


Pgina con varios tutoriales sobre Java y threads. http://programacion.com/java/tutorial/threads/ [ltima vez visitado, 8-6-2004] Pgina web en espaol dedicada ntegramente al lenguaje Java. http://www.javahispano.org/ [ltima vez visitado, 8-6-2004] El rincn de Java.

http://wwws.uib.es/rincon/java/ [ltima vez visitado, 8-6-2004]

23

Programacin Orientada a Objetos - 2004

También podría gustarte