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 Mi Thread extends Thread {
public void run() {
. . .
}
}
se corresponde con la declaración de un clase, Mi Thread, que extiende la clase Thread,
sobrecargando el método Thread.run heredado con su propia implementación.
González et al.
5
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 Thread Death?), 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 Mi Thread 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 Mi Thread 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.
Por otro lado, es un error pensar que la interfaz Runnable está realizando alguna tarea
mientras un hilo de alguna clase que la implemente se está ejecutando. Es una interfaz, y como
tal, sólo contiene funciones abstractas (concretamente una única, run), proporcionando tan solo
una idea de diseño, de infraestructura, de la clase Thread, pero ninguna funcionalidad. Su
declaración en Java tiene el siguiente aspecto:
package Java.lang;
public interfaz Runnable {
public abstract void run() ;
}
Comentados los aspectos más importantes de la interfaz Runnable, veamos ahora la
definición de la clase Thread, de la cual podemos deducir lo que realmente se está haciendo:
public class Thread implements Runnable {
…
public void run() {
Java Threads (Hilos en Java)
6
if( tarea != null )
tarea.run() ;
}
…}
Se deduce, por tanto, que la propia clase Thread de Java también implementa la interfaz
Runnable. Observamos que en el método run de Thread se comprueba si la clase con que
se está trabajando en ese momento (tarea), que es la clase que se pretende ejecutar como hilo,
es o no nula. En caso de no ser nula, se invoca al método run propio de dicha clase.
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 Test Th( “Thread 1″,(int)(Math.random()*2000) );
siendo Test Th 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 Test Th (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.
González et al.
9
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ó).
Estamos hablando de la función miembro isAlive.
t1.isAlive();
Devolverá true en caso de que el hilo 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. En otro caso, lógicamente, devolverá false.