miércoles, 10 de noviembre de 2010

Hilos

Hilo.
Es un único flujo de ejecución dentro de un proceso.
Un proceso es un programa ejecutándose dentro de su propio espacio de direcciones. Un hilo es una secuencia de código en ejecución dentro del contexto de un proceso. Los hilos no pueden ejecutarse ellos solos; requieren la supervisión de un proceso padre para correr. Dentro de cada proceso hay varios hilos ejecutándose.
Los hilos a menudo son conocidos o llamados procesos ligeros. Un hilo, en efecto, es muy similar a un proceso pero con la diferencia de que un hilo siempre corre dentro del contexto de otro programa. Por el contrario, los procesos mantienen su propio espacio de direcciones y entorno de operaciones. Los hilos dependen de un programa padre en lo que se refiere a recursos de ejecución. La siguiente figura muestra le relación entre hilos y procesos.
Clases.


Thread
La clase Thread es la clase responsable de producir hilos funcionales para otras clases. Para añadir la funcionalidad de hilo a una clase simplemente se deriva la clase de Thread y se ignora el método run. Es en este método run donde el procesamiento de un hilo toma lugar, y a menudo se refieren a él como el cuerpo del hilo. La clase Thread también define los métodos start y stop, los cuales te permiten comenzar y parar la ejecución del hilo, además de un gran número de métodos útiles.

Runnable
Java no soporta herencia múltiple de forma directa, es decir, no se puede derivar una clase de varias clases padre. Esto nos plantea la duda sobre cómo podemos añadir la funcionalidad de Hilo a una clase que deriva de otra clase, siendo ésta distinta de Thread. Para lograr esto se utiliza la interfaz Runnable. La interfaz Runnable proporciona la capacidad de añadir la funcionalidad de un hilo a una clase simplemente implementando la interfaz, en lugar de derivándola de la clase Thread.
Las clases que implementan la interfaz Runnable proporcionan un método run que es ejecutado por un objeto hilo asociado que es creado aparte. Esta es una herramienta muy útil y a menudo es la única salida que tenemos para incorporar multihilo dentro de las clases. Esta cuestión será tratada más ampliamente en el apartado de Creación de hilos.
ThreadDeath
La clase de error ThreadDeath proporciona un mecanismo que permite hacer limpieza después de que un hilo haya sido finalizado de forma asíncrona. Se llama a ThreadDeath una clase error porque deriva de la clase Error, la cual proporciona medios para manejar y notificar errores. Cuando el método stop de un hilo es invocado, una instancia de ThreadDeath es lanzada por el moribundo hilo como un error. Sólo se debe recoger el objeto ThreadDeath si se necesita para realiza una limpieza específica a la terminación asíncrona, lo cual es una situación bastante inusual. Si se recoge el objeto, debe ser relanzado para que el hilo realmente muera.

Creación de threads derivando de la clase Thread

Considérese el siguiente ejemplo de declaración de una nueva clase:
public class SimpleThread extends Thread {
// constructor
public SimpleThread (String str) {
super(str);
}
// redefinición del método run()
public void run() {
for(int i=0;i<10;i++)
System.out.println("Este es el thread : " + getName());
}
}
En este caso, se ha creado la clase SimpleThread, que hereda de Thread. En su constructor se utiliza un String (opcional) para poner nombre al nuevo thread creado, y mediante super() se llama al constructor de la super-clase Thread. Asimismo, se redefine el método run(), que define la principal actividad del thread, para que escriba 10 veces el nombre del thread creado.
Para poner en marcha este nuevo thread se debe crear un objeto de la clase SimpleThread, y llamar al método start(),heredado de la super-clase Thread, que se encarga de llamar a run(). Por ejemplo:
SimpleThread miThread = new SimpleThread(“Hilo de prueba”);
miThread.start();

Creación de threads implementando la interface Runnable

Esta segunda forma también requiere que se defina el método run(), pero además es necesario crear un objeto de la clase Thread para lanzar la ejecución del nuevo hilo. Al constructor de la clase Thread hay que pasarle una referencia del objeto de la clase que implementa la interface Runnable.
Posteriormente, cuando se ejecute el método start() del thread, éste llamará al método run() definido en la nueva clase. A continuación se muestra el mismo estilo de clase que en el ejemplo anterior implementada mediante la interface Runnable:
public class SimpleRunnable implements Runnable {
// se crea un nombre
String nameThread;
// constructor
public SimpleRunnable (String str) {
nameThread = str;
}
// definición del método run()
public void run() {
for(int i=0;i<10;i++)
System.out.println("Este es el thread: " + nameThread);
}
}

CICLO DE VIDA DE UN THREAD
En el apartado anterior se ha visto cómo crear nuevos objetos que permiten incorporar en un programa la posibilidad de realizar varias tareas simultáneamente. En la Figura se muestran los distintos estados por los que puede pasar un thread a lo largo de su vida. Un thread puede presentar cuatro estados distintos:
1. Nuevo (New): El thread ha sido creado pero no inicializado, es decir, no se ha ejecutado todavía el método start(). Se producirá un mensaje de error (IllegalThreadStateException) si se intenta ejecutar cualquier método de la clase Thread  distinto de start().
2. Ejecutable (Runnable): El thread puede estar ejecutándose, siempre y cuando se le haya asignado un determinado tiempo de CPU. En la práctica puede no estar siendo ejecutado en un instante determinado en beneficio de otro thread.
3. Bloqueado (Blocked o Not Runnable): El thread podría estar ejecutándose, pero hay alguna actividad interna suya que lo impide, como por ejemplo una espera producida por una operación de escritura o lectura de datos por teclado (E/S). Si un thread está en este estado, no se le asigna tiempo de CPU.
4. Muerto (Dead): La forma habitual de que un thread muera es finalizando el método run(). También puede llamarse al método stop() de la clase Thread, aunque dicho método es considerado “peligroso” y no se debe utilizar.







CREACIÓN DE HILOS
En Java, los hilos comparten el mismo espacio de memoria. Incluso comparten gran parte del entorno de ejecución, de modo que la creación de nuevos hilos es mucho más rápida que la creación de nuevos procesos. La ventaja que proporcionan los hilos es la capacidad de tener más de un camino de ejecución en un mismo programa. Así, con un único proceso, ejecutándose una JVM (Java Virtual Machine), habrá siempre más de un hilo, cada uno con su propio camino de ejecución.
En cuanto al proceso de creación de hilos, son dos los mecanismos que nos permiten llevarlo a cabo en Java: implementando la interfaz Runnable, o extendiendo la clase Thread, esto es, creando una subclase de esta clase.
Lo más habitual es crear hilos implementando la interfaz Runnable, dado que las interfaces representan una forma de encapsulamiento del trabajo que una clase debe realizar.
Así, se utilizan para el diseño de requisitos comunes a todas las clases que se tiene previsto implementar. La interfaz define el trabajo, la funcionalidad que debe cubrirse, mientras que la clase o clases que implementan la interfaz realizan dicho trabajo (cumplen esa funcionalidad).
Todas las clases o grupos de clases que implementen una cierta interfaz deberán seguir las mismas reglas de funcionamiento.
El otro mecanismo de creación de hilos, como ya hemos dicho, consistiría en la creación previa de una subclase de la clase Thread, la cual podríamos instanciar después.
Por ejemplo,
class MiThread extends Thread {
public void run() {
. . .
}
}
se corresponde con la declaración de un clase, MiThread, que extiende la clase Thread, sobrecargando el método Thread.run heredado con su propia implementación.

Es en el método run donde se implementa el código correspondiente a la acción (la tarea) que el hilo debe desarrollar. El método run no es invocado directa o explícitamente (a menos que no quieras que se ejecute dentro de su propio hilo). En lugar de esto, los hilos se arrancan con el método start, se suspenden con el método suspend, se reanudan con el método resume, y se detienen con el método stop (el cual supone también la muerte del hilo y la correspondiente excepción ThreadDeath), como ya explicaremos en el apartado de Estado y Control de Hilos. Un hilo suspendido puede reanudarse en la instrucción del método run en la que fue suspendido.
En el caso de crear un hilo extendiendo la clase Thread, se pueden heredar los métodos y variables de la clase padre. Si es así, una misma subclase solamente puede extender o derivar una vez de la clase padre Thread. Esta limitación de Java puede ser superada a través de la implementación de Runnable. Veamos el siguiente ejemplo:
public class MiThread implements Runnable {
Thread t;
public void run() {
// Ejecución del thread una vez creado
}
}
En este caso necesitamos crear una instancia de Thread antes de que el sistema pueda ejecutar el proceso como un hilo. Además, el método abstracto run que está definido en la interfaz Runnable tiene que implementarse en la nueva clase creada.
La diferencia entre ambos métodos de creación de hilos en Java radica en la flexibilidad con que cuenta el programador, que es mayor en el caso de la utilización de la interfaz Runnable.
Sobre la base del ejemplo anterior, se podría extender la clase MiThread a continuación, si fuese necesario. La mayoría de las clases creadas que necesiten ejecutarse como un hilo implementarán la interfaz Runnable, ya que así queda cubierta la posibilidad de que sean extendidas por otras clases.

ESTADOS DE UN HILO
El comportamiento de un hilo depende del estado en que se encuentre, este estado define su modo de operación actual, por ejemplo, si esta corriendo o no. A continuación proporcionamos la relación de estados en los que puede estar un hilo Java.
• New
• Runnable
• Not running
• Dead
New
Un hilo esta en el estado new la primera vez que se crea y hasta que el método start es llamado. Los hilos en estado new ya han sido inicializados y están listos para empezar a trabajar, pero aún no han sido notificados para que empiecen a realizar su trabajo.
Runnable
Cuando se llama al método start de un hilo nuevo, el método run es invocado y el hilo entra en el estado runnable. Este estado podría llamarse “running” porque la ejecución del método run significa que el hilo esta corriendo. Sin embargo, debemos tener en cuenta la prioridad de los hilos. Aunque cada hilo está corriendo desde el punto de vista del usuario, en realidad todos los hilos, excepto el que en estos momentos esta utilizando la CPU, están en el estado runnable (ejecutables, listos para correr) en cualquier momento dado. Uno puede pensar conceptualmente en el estado runnable como si fuera el estado “running”, sólo tenemos que recordar que todos los hilos tienen que compartir los recursos del sistema.
Not running
El estado not running se aplica a todos los hilos que están parados por alguna razón. Cuando un hilo está en este estado, está listo para ser usado y es capaz de volver al estado runnable en un momento dado. Los hilos pueden pasar al estado not running a través de varias vías.
A continuación se citan diferentes eventos que pueden hacer que un hilo esté parado de modo temporal.
• El método suspend ha sido llamado
• El método sleep ha sido llamado
• El método wait ha sido llamado
• El hilo esta bloqueado por I/O
Para cada una de estas acciones que implica que el hilo pase al estado not running hay una forma para hacer que el hilo vuelva a correr. A continuación presentamos la lista de eventos correspondientes que pueden hacer que el hilo pase al estado runnable.
• Si un hilo está suspendido, la invocación del método resume
• Si un hilo está durmiendo, pasarán el número de milisegundos que se ha especificado que debe dormir
• Si un hilo está esperando, la llamada a notify o notifyAll por parte del objeto por el que espera
• Si un hilo está bloqueado por I/O, la finalización de la operación I/O en cuestión
Dead
Un hilo entra en estado dead cuando ya no es un objeto necesario. Los hilos en estado dead no pueden ser resucitados y ejecutados de nuevo. Un hilo puede entrar en estado dead a través de dos vías:
• El método run termina su ejecución.
• El método stop es llamado.
La primera opción es el modo natural de que un hilo muera. Uno puede pensar en la muerte de un hilo cuando su método run termina la ejecución como una muerte por causas naturales.
En contraste a esto, está la muerte de un hilo “por causa” de su método stop. Una llamada al método stop mata al hilo de modo asíncrono.
Aunque la segunda opción suene un poco brusca, a menudo es muy útil. Por ejemplo, es bastante común que los applets maten sus hilos utilizando el método stop cuando el propio método stop del applet ha sido invocado. La razón de esto es que el método stop del applet es llamado normalmente como respuesta al hecho de que el usuario ha abandonado la página web que contenía el applet y no es adecuado dejar hilos de un applet corriendo cuando el applet no está activo, así que es deseable matar los hilos.
Control de un hilo
Arranque de un hilo
En el contexto de las aplicaciones, sabemos que es main la primera función que se invoca tras arrancar, y por tanto, lógicamente, es el lugar más apropiado para crear y arrancar otros hilos.
La línea de código:
t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) );
Siendo TestTh una subclase de la clase Thread (o una clase que implemente la interfaz Runnable) crea un nuevo hilo. Los dos argumentos pasados, sin mayor relevancia, satisfarán el prototipo del constructor de la clase y se utilizarán para la inicialización del objeto.
Al tener control directo sobre los hilos, tenemos que arrancarlos explícitamente. Como ya se comentó anteriormente, es la función miembro start la que nos permite hacerlo. En nuestro ejemplo sería:
t1.start();
start, en realidad es un método oculto en el hilo que llama al método run.

Manipulación de un hilo
Si todo fue bien en la creación del objeto TestTh (t1), éste debería contener un hilo, una traza de ejecución válida, que controlaremos en el método run del objeto.
El cuerpo de esta función miembro viene a ser el cuerpo de un programa como ya los conocemos. Digamos que es la rutina main a nivel de hilo. Todo lo que queremos que haga el hilo debe estar dentro del método run. Cuando finalice run, finalizará también el hilo que lo ejecutaba.
Suspensión de un Hilo
La función miembro suspend de la clase Thread permite tener un control sobre el hilo de modo que podamos desactivarlo, detener su actividad durante un intervalo de tiempo indeterminado, a diferencia del uso de la llamada al sistema sleep, que simplemente lleva al hilo a un estado de “dormido”, y siempre durante un número de milisegundos concreto.
Este método puede resultar útil si, construyendo un applet con un hilo de animación, queremos permitir al usuario detener (que no finalizar) la animación, hasta que éste decida reanudarla.
Este método no detiene la ejecución permanentemente. El hilo es suspendido indefinidamente y para volver a activarlo de nuevo necesitamos realizar una invocación a la función miembro resume.
Parada de un Hilo
Ya conocemos los métodos de control de hilos que nos permiten arrancarlos, suspenderlos y reanudarlos. El último elemento de control que se necesita sobre hilos es el método stop, utilizado para terminar la ejecución de un hilo de forma permanente:
t1.stop();
Señalar que esta llamada no destruye el hilo, sino que detiene su ejecución, y ésta no puede reanudarse con el método start. Cuando se desasignen las variables que se usan en el hilo, el objeto hilo (creado con new) quedará marcado para eliminarlo y el garbage collector (recolector de basura de Java) se encargará de liberar la memoria que utilizaba.
Tiene sentido su utilidad, por ejemplo, en aplicaciones complejas que necesiten un control sobre cada uno de los hilos que se lancen.
Por último, un método de control de hilos que nos permite comprobar si una instancia está viva (el hilo se ha arrancado y aún no se ha detenido) o no (bien no se arrancó; bien ya finalizó).

Creación de threads derivando de la clase Thread

Considérese el siguiente ejemplo de declaración de una nueva clase:

public class SimpleThread extends Thread {
// constructor
public SimpleThread (String str) {
super(str);
}
// redefinición del método run()
public void run() {
for(int i=0;i<10;i++)
System.out.println("Este es el thread : " + getName());
}
}
En este caso, se ha creado la clase SimpleThread, que hereda de Thread. En su constructor se utiliza un String (opcional) para poner nombre al nuevo thread creado, y mediante super() se llama al constructor de la super-clase Thread. Asimismo, se redefine el método run(), que define la principal actividad del thread, para que escriba 10 veces el nombre del thread creado.
Para poner en marcha este nuevo thread se debe crear un objeto de la clase SimpleThread, y llamar al método start(),heredado de la super-clase Thread, que se encarga de llamar a run(). Por ejemplo:
SimpleThread miThread = new SimpleThread(“Hilo de prueba”);
miThread.start();

Creación de threads implementando la interface Runnable

Esta segunda forma también requiere que se defina el método run(), pero además es necesario crear un objeto de la clase Thread para lanzar la ejecución del nuevo hilo. Al constructor de la clase Thread hay que pasarle una referencia del objeto de la clase que implementa la interface Runnable.
Posteriormente, cuando se ejecute el método start() del thread, éste llamará al método run() definido en la nueva clase. A continuación se muestra el mismo estilo de clase que en el ejemplo anterior implementada mediante la interface Runnable:
public class SimpleRunnable implements Runnable {
// se crea un nombre
String nameThread;
// constructor
public SimpleRunnable (String str) {
nameThread = str;
}
// definición del método run()
public void run() {
for(int i=0;i<10;i++)
System.out.println("Este es el thread: " + nameThread);
}
}