HILOS Conceptos basicos. Un hilo -algunas veces llamado contexto de ejecución o proceso ligero- es un flujo de control secuencial dentro de un programa. Un único hilo es similar a un programa secuencial; es decir, tiene un comienzo, una secuencia y un final, además en cualquier momento durante la ejecución existe un sólo punto de ejecución. 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. El ambiente de desarrollo de Java soporta programación con múltiples hilos por medio de bibliotecas, el lenguaje mismo y con la ayuda del sistema de tiempo de ejecución. A continuación se listan las características más importantes con las que cuenta Java para soportar el uso de hilos. El método run Antes que nada, necesitamos proveer a cada hilo con un método run para indicarle qué debe hacer. El código de este método implementa el comportamiento en ejecución del hilo y puede hacer, prácticamente cualquier cosa capaz de ser codificada en Java. Existen dos técnicas para proveer un método run para un hilo: Haciendo una subclase de Thread y sobrecargando run Implementando la interface Runnable. Thread es una clase, pero Runnable es una interfase. Por convención para seleccionar cual técnica vamos a usar seguiremos la siguiente regla: ``Si las clases que elaboras deben ser subclases de otras clases (un caso común es Applet), entonces debe usarse Runnable.'' Una vez que el hilo hace algo, podemos manejar el ciclo de vida de un hilo: como crear y arrancar un hilo, algunas cosas especiales que podemos hacer mientras se ejecuta y como detenerlo. Manejo de Prioridades de los hilos La gran ventaja que ofrecen los hilos (como se puede inferir de lo anteriormente expuesto) es que corren de manera concurrente. Conceptualmente, esto es cierto, pero en la práctica generalmente no es posible. La mayoría de las computadoras tienen un sólo procesador, por lo tanto los hilos corren uno a la vez de forma tal que proveen la ilusión de concurrencia (esto es llamado scheduling). El sistema de ejecución de Java soporta un algoritmo determinístico (para el scheduler) llamado fixed priority scheduling. Este algoritmo asigna tiempo de ejecución a un hilo basado en su prioridad relativa a los demás hilos que están listos para ejecutarse. Cuando se crea un nuevo hilo, hereda su prioridad del hilo que lo crea, ésta puede ser modificada con el método setPriority. Las prioridades son enteros entre MIN_PRIORITY y MAX_PRIORITY (constantes definidas en la clase Thread). Entre más alto el entero, más alta la prioridad. Si dos hilos con la misma prioridad están esperando que el CPU los ejecute, el scheduler escoge uno utilizando round-robin (i.e. escoge de forma aleatoria, se supone que round-robin ofrece iguales probabilidades de ejecución a los hilos en cuestión). El hilo seleccionado para ejecución, corre hasta que alguna de estas condiciones sea verdadera: Un hilo con mayor prioridad está listo para ejecución. El hilo cede su lugar (yields), o su método run termina. En sistemas que soportan rebanadas de tiempo (time slicing), su tiempo asignado ha expirado. En ese momento el segundo hilo es atendido por el CPU y así sucesivamente hasta que el intérprete termina. Sincronización. Cuando tenemos varios hilos, muchas veces deseamos que éstos pueden compartir datos, por lo tanto, es indispensable saber sincronizar sus actividades; esto también nos permitirá evitar inanición y abrazos mortales. En muchas situaciones, hilos que se ejecutan concurrentemente comparten información y deben considerar el estado de las actividades de los demás hilos. Los segmentos de código dentro de un programa que accedan el mismo objeto desde hilos separados (concurrentes) se llaman regiones críticas. Agrupamientos de hilos Todo hilo en Java es miembro de un grupo. Un grupo de hilos nos da las herramientas para meter varios hilos en un sólo objeto y manipularlos (a todos) al mismo tiempo. La clase ThreadGroup implementa los grupos de hilos en Java. El sistema de ejecución de Java pone a un hilo en un grupo durante la construcción del hilo. Cuando se crea un hilo, uno puede escoger el grupo al que pertenecerá o permitir que el sistema seleccione un grupo razonable por omisión para nuestro nuevo hilo. El hilo así creado es miembro permanente del grupo al cual se una durante su creación -no puede ser cambiado. Si se crea un hilo sin especificar su grupo en el constructor, el sistema de ejecución automáticamente pone el nuevo hilo en el mismo grupo que el hilo que lo crea. Cuando una aplicación de Java arranca, el sistema de ejecución de Java crea un ThreadGroup llamado main. A menos que se especifique lo contrario, todos los nuevos hilos que se creen durante el desarrollo de la aplicación serán miembros del grupo de hilos main. La clase Thread provee tres constructores que te permiten asignar a un grupo al hilo que estás creando. Finalmente la clase ThreadGroup provee un conjunto de métodos que te permiten obtener información como: qué otros hilos pertenecen al mismo grupo, modificar los hilos por grupo: suspenderlos, activarlos, detenerlos, etc. todo con una sola invocación a los métodos respectivos. Creación de un hilo. Esta es solo una forma de hacerlo heredando de Thread class MiThread extends Thread { public void run() { . . . } El ejemplo anterior crea una nueva clase MiThread que extiende la clase Thread y sobrecarga el método Thread.run() por su propia implementacion. La otra forma es utilizando la interface Runnable public class MiThread implements Runnable { Thread t; public void run() { // Ejecución del thread una vez creado } } Arranque de un Thread Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el lugar natural para crear y arrancar otros threads. La línea de código: t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) ); crea un nuevo thread. Los dos argumentos representan el nombre del thread y el tiempo que queremos que espere antes de imprimir el mensaje. Al tener control directo sobre los threads, tenemos que arrancarlos explícitamente. En nuestro ejemplo con: t1.start(); start(), en realidad es un método oculto en el thread que llama al método run(). Suspensión de un Thread Puede resultar útil suspender la ejecución de un thread sin marcar un límite de tiempo. Por ejemplo, para construir un applet con un thread de animación, querrá permitir al usuario la opción de detener la animación hasta que quiera continuar. No se trata de terminar la animación, sino desactivarla. Para este tipo de control de thread se puede utilizar el método suspend(). t1.suspend(); Este método no detiene la ejecución permanentemente. El thread es suspendido indefinidamente y para volver a activarlo de nuevo necesitamos realizar una invocación al método resume(): t1.resume(); Parada de un Thread El último elemento de control que se necesita sobre threads es el método stop(). Se utiliza para terminar la ejecución de un thread: t1.stop(); Esta llamada no destruye el thread, sino que detiene su ejecución. La ejecución no se puede reanudar ya con t1.start(). Cuando se desasignen las variables que se usan en el thread, el objeto thread (creado con new) quedará marcado para eliminarlo y el garbage collector se encargará de liberar la memoria que utilizaba. En nuestro ejemplo, no necesitamos detener explícitamente el thread. Simplemente se le deja terminar. Los programas más complejos necesitarán un control sobre cada uno de los threads que lancen, el método stop() puede utilizarse en esas situaciones. Si se necesita, se puede comprobar si un thread está vivo o no; considerando vivo un thread que ha comenzado y no ha sido detenido. t1.isAlive(); Este método devolverá true en caso de que el thread t1 esté vivo, es decir, ya se haya llamado a su método run() y no haya sido parado con un stop() ni haya terminado el método run() en su ejecución. Ciclo de vida de un hilo Un hilo puede estar en diferentes estados, por ejemplo, si un hilo esta en el estado born (fig. 1) permanece ahí hasta que se llama al método start, que pone al hilo en el estado de ready . El hilo que tenga la prioridad más alta entra al estado de running (es cuando el hilo comienza ha ejecutarse). El hilo entra al estado dead cuando el método run termina ya sea por que completo sus actividades o por cualquier otra razón. Una manera común de que un hilo entre al estado blocked es cuando un hilo solicita entrada o salida de datos. En este caso el hilo entra al estado ready cuando la entrada/salida se completa. Cuando el programa llama al método sleep de un hilo que esta corriendo, el hilo entra al estado sleeping . Un hilo que se encuentra en este estado se cambia al estado ready cuando expira el tiempo designado para dormir. Si el programa ejecuta el método interrupt de un hilo que se encuentra en el estado sleeping el hilo se sale de ese estado y entra al estado ready. Un hilo entra al estado dead cuando el método run termina o cuando se lanza una excepción. born start Termina solicitud entrada/salida Notify o nitifyAll ready wait running Solicita entrada/salida sleep wating Termina intervalo de dormir sleeping dead bloked