2.3. Hilos

Un hilo es un flujo de control dentro de un programa. Creando varios hilos podremos realizar varias tareas simultáneamente. Cada hilo tendrá sólo un contexto de ejecución (contador de programa, pila de ejecución). Es decir, a diferencia de los procesos UNIX, no tienen su propio espacio de memoria sino que acceden todos al mismo espacio de memoria común, por lo que será importante su sincronización cuando tengamos varios hilos accediendo a los mismos objetos.

2.3.1. Creación de hilos

En Java los hilos están encapsulados en la clase Thread. Para crear un hilo tenemos dos posibilidades:

En ambos casos debemos definir un método run() que será el que contenga el código del hilo. Desde dentro de este método podremos llamar a cualquier otro método de cualquier objeto, pero este método run() será el método que se invoque cuando iniciemos la ejecución de un hilo. El hilo terminará su ejecución cuando termine de ejecutarse este método run().

Para crear nuestro hilo mediante herencia haremos lo siguiente:

public class EjemploHilo extends Thread
{
    public void run() {
        // Código del hilo
    }
}

Una vez definida la clase de nuestro hilo deberemos instanciarlo y ejecutarlo de la siguiente forma:

Thread t = new EjemploHilo();
t.start();

Crear un hilo heredando de Thread tiene el problema de que al no haber herencia múltiple en Java, si heredamos de Thread no podremos heredar de ninguna otra clase, y por lo tanto un hilo no podría heredar de ninguna otra clase.

Este problema desaparece si utilizamos la interfaz Runnable para crear el hilo, ya que una clase puede implementar varios interfaces. Definiremos la clase que contenga el hilo como se muestra a continuación:

public class EjemploHilo implements Runnable
{
    public void run() {
        // Código del hilo
    }
}

Para instanciar y ejecutar un hilo de este tipo deberemos hacer lo siguiente:

Thread t = new Thread(new EjemploHilo());
t.start();

Esto es así debido a que en este caso EjemploHilo no deriva de una clase Thread, por lo que no se puede considerar un hilo, lo único que estamos haciendo implementando la interfaz es asegurar que vamos a tener definido el método run(). Con esto lo que haremos será proporcionar esta clase al constructor de la clase Thread, para que el objeto Thread que creemos llame al método run() de la clase que hemos definido al iniciarse la ejecución del hilo, ya que implementando la interfaz le aseguramos que esta función existe.

2.3.2. Estado y propiedades de los hilos

Un hilo pasará por varios estados durante su ciclo de vida.

Thread t = new Thread(this);

Una vez se ha instanciado el objeto del hilo, diremos que está en estado de Nuevo hilo.

t.start();

Cuando invoquemos su método start() el hilo pasará a ser un hilo vivo, comenzándose a ejecutar su método run(). Una vez haya salido de este método pasará a ser un hilo muerto.

La única forma de parar un hilo es hacer que salga del método run() de forma natural. Podremos conseguir esto haciendo que se cumpla la condición de salida del bucle principal definido dentro del run(). Las funciones para parar, pausar y reanudar hilos estás desaprobadas en las versiones actuales de Java.

Mientras el hilo esté vivo, podrá encontrarse en dos estados: Ejecutable y No ejecutable. El hilo pasará de Ejecutable a No ejecutable en los siguientes casos:

Figura 2. Ciclo de vida de los hilos

Lo único que podremos saber es si un hilo se encuentra vivo o no, llamando a su método isAlive().

Además, una propiedad importante de los hilos será su prioridad. Mientras el hilo se encuentre vivo, el scheduler de la máquina virtual Java le asignará o lo sacará de la CPU, coordinando así el uso de la CPU por parte de todos los hilos activos basándose en su prioridad. Se puede forzar la salida de un hilo de la CPU llamando a su método yield(). También se sacará un hilo de la CPU cuando un hilo de mayor prioridad se haga Ejecutable, o cuando el tiempo que se le haya asignado expire.

Para cambiar la prioridad de un hilo se utiliza el método setPriority(), al que deberemos proporcionar un valor de prioridad entre MIN_PRIORITY y MAX_PRIORITY.

2.3.3. Sincronización de hilos

Muchas veces los hilos deberán trabajar de forma coordinada, por lo que es necesario un mecanismo de sincronización entre ellos.

Un primer mecanismo de comunicación es la variable cerrojo incluida en todo objeto Object, que permitirá evitar que más de un hilo entre en la sección crítica. Los métodos declarados como synchronized utilizan el cerrojo de la clase a la que pertenecen evitando que más de un hilo entre en ellos al mismo tiempo.

public synchronized void seccion_critica()
{
    // Código sección crítica
}

También podemos utilizar cualquier otro objeto para la sincronización dentro de nuestro método de la siguiente forma:

synchronized (objeto_con_cerrojo) 
{
    // Código sección crítica
}

Además podemos hacer que un hilo quede bloqueado a la espera de que otro hilo lo desbloquee cuando suceda un determinado evento. Para bloquear un hilo usaremos la función wait(), para lo cual el hilo que llama a esta función debe estar en posesión del monitor, cosa que ocurre dentro de un método synchronized, por lo que sólo podremos bloquear a un proceso dentro de estos métodos.

Para desbloquear a los hilos que haya bloqueados se utilizará notifyAll(), o bien notify() para desbloquear sólo uno de ellos aleatoriamente. Para invocar estos métodos ocurrirá lo mismo, el hilo deberá estar en posesión del monitor.

Cuando un hilo queda bloqueado liberará el cerrojo para que otro hilo pueda entrar en la sección crítica y desbloquearlo.

Por último, puede ser necesario esperar a que un determinado hilo haya finalizado su tarea para continuar. Esto lo podremos hacer llamando al método join() de dicho hilo, que nos bloqueará hasta que el hilo haya finalizado.

2.3.4. Grupos de hilos

Los grupos de hilos nos permitirán crear una serie de hilos y manejarlos todos a la vez como un único objeto. Si al crear un hilo no se especifica ningún grupo de hilos, el hilo creado pertenecerá al grupo de hilos por defecto.

Podemos crearnos nuestro propio grupo de hilos instanciando un objeto de la clase ThreadGroup. Para crear hilos dentro de este grupo deberemos pasar este grupo al constructor de los hilos que creemos.

ThreadGroup grupo = new ThreadGroup("Grupo de hilos");
Thread t = new Thread(grupo,new EjemploHilo());