
Los hilos se crean como cualquier otro objeto de Java y duran hasta que finaliza el método run(). Todo hilo pasa por cuatro estados: creado, ejecutable, bloqueado, eliminado.
- Creado. Este es el estado del hilo cuando se llama al constructor de la clase hilo. Todavía no tiene recursos asignados.
- Ejecutable. A este estado se accede cuando el hilo creado llama a start(). Este método asigna recursos para la ejecución del hilo, planifica su ejecución y automáticamente llama al método run(), que es donde se escriben las acciones a realizar por el hilo. Puede haber varios hilos ejecutándose; en un momento dado es uno de ellos el que está en ejecución, los demás se encuentran ejecutándose.
- Bloqueado. Un hilo en estado bloqueado no realiza acción alguna, no recibe ciclos de CPU. Hasta que no se desbloquea no se le considera en la distribución de tiempos de ejecución. Un hilo pasa a estado bloqueado cuando llama al método sleep() para dormirlo n milisegundos; o bien llamadas a otros métodos como wait(), join() o yield(). También, un hilo se bloquea cuando está a la espera de una operación de entrada/salida.
- Eliminado. Una vez que el método run() termina de ejecutarse el hilo pasa a estado eliminado. La clase Thread dispone del método stop() para que un hilo pase a este estado; sin embargo desde Java 2 se desaconseja utilizar este método por el riesgo de que queden objetos y recursos no liberados.
Los hilos, una vez creados, pasan cíclicamente del estado ejecutable a bloqueado. El sistema se ocupa de asignar tiempos de CPU a los hilos en estado ejecutable. Cuando están en ejecución, es el método run() del hilo el que se ejecuta. Las acciones que bloquean temporalmente un hilo son:
- Llamada al método sleep() de la clase Thread. Este método detiene la ejecución del hilo durante un número de milisegundos. La declaración del método es:
void sleep(long mills) throws InterruptedException
- Ejecutar el método wait(). Esta acción produce que el hilo se quede a la espera de que suceda un hecho que permita seguir con la ejecución del hilo. Para que el hilo vuelva al estado ejecutable tiene que recibir una notificación, mediante la llamada a notify(), o bien notifyAll(). Estos dos métodos, y también wait(), están definidos en la clase Object. Su declaración es:
final void notify()
final void notifyAll()
final void wait() throws InterruptedException
final void wait(long mensaje) throws InterruptedException
- Estar a la espera de operaciones de entrada/salida (leer un archivo, . . .).
- Cuando desde un hilo un objeto llame a un método declarado con el atributo synchronized y dicho objeto esté bloqueado por otro hilo.
Normalmente el método run() se diseña con un bucle cuya ejecución depende de una variable boolean, de tal forma que cuando se quiera detener cambie el estado de la variable. La clase Thread dispone del método isAlive() que devuelve true si el hilo se encuentra en estado ejecutable o bloqueado. Un hilo activo no quiere decir que esté en ejecución, sino que no está eliminado y por consiguiente se puede utilizar. La declaración del método isAlive() es:
boolean isAlive();
Otro método de interés en el control de los hilos es join(). Un hilo espera la ejecución de otro hilo mediante el uso de join(). Este método hace que se bloquee la ejecución del hilo hasta que termine la ejecución del otro hilo que llama a join() (otroHilo.join()).
Ciclo de vida de un hilo.

Ejemplo
El programa crea tres hilos desde el hilo principal, es decir, desde main(). La ejecución del programa permite activar una llamada a join(). Los resultados de la ejecución ponen de manifiesto la funcionalidad de join(). La clase Tareas define el hilo, implementa la interfaz Runnable. El método run() tiene un bucle con un mensaje a pantalla y llamada a sleep() con el fin de que el estado del hilo pase a bloqueado y se ejecute otro hilo.
public class Tareas implements Runnable{
private String mensaje;
private Thread hilo;
public Tareas(String mensaje){
this.mensaje = mensaje;
hilo = new Thread(this,mensaje);
System.out.println(«Creación de hilo: » + hilo);
hilo.start(); // ejecutándose
}
@Override
public void run() {
try{
for(int i=6;i>0;i-=2){
System.out.println(«Hilo: » +hilo+ «i = » + i);
hilo.sleep(1000); // Hilo «durmiendo», queda bloqueado
}
}catch(InterruptedException ex) {
ex.printStackTrace();
}
System.out.println(«Finaliza hilo: » + mensaje);
}
}
import java.util.Scanner;
public class TresHilosJoin{
public static void main(String[] args){
Scanner entrada = new Scanner(System.in);
int opcion;
do{
System.out.println(«[1]. Ejecutar con join()»);
System.out.println(«[2]. Ejecutar sin join()»);
System.out.print(«Ingresar opción(1-2): «);
opcion = entrada.nextInt();
}while(opcion!=1 && opcion!=2);
System.out.println(«—————————————————«);
// se crean tres hilos
Tareas t1, t2, t3;
t1 = new Tareas(«Primero»);
t2 = new Tareas(«Segundo»);
t3 = new Tareas(«Tercero»);
System.out.println(«Hilo ejecutándose: » + Thread.currentThread());
if(opcion==1){
System.out.println(«Cada hilo llamada a join()»);
try{
t1.hilo.join(); // Hilo actual «bloqueado» hasta finalizar t1
t2.hilo.join();
t3.hilo.join();
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
else
System.out.println(«No hay llamada a join()»);
//
System.out.println(«Finaliza hilo main()»);
}
}
Al ejecutar el programa con la opción 1 se obtiene:
[1]. Ejecutar con join()
[2]. Ejecutar sin join()
Ingresar opción(1-2): 1
————————————————————–
Creación de hilo: Thread[Primero,5,main]
Creación de hilo: Thread[Segundo,5,main]
Creación de hilo: Thread[Tercero,5,main]
Hilo ejecutándose: Thread[main,5,main]
Cada hilo llamada a join()
Hilo: Thread[Primero,5,main] i = 6
Hilo: Thread[Segundo,5,main] i = 6
Hilo: Thread[Tercero,5,main] i = 6
Hilo: Thread[Segundo,5,main] i = 4
Hilo: Thread[Tercero,5,main] i = 4
Hilo: Thread[Primero,5,main] i = 4
Hilo: Thread[Primero,5,main] i = 2
Hilo: Thread[Segundo,5,main] i = 2
Hilo: Thread[Tercero,5,main] i = 2
Finaliza hilo: Primero
Finaliza hilo: Segundo
Finaliza hilo: Tercero
Finaliza hilo main()
Al ejecutar el programa con la opción 2 se obtiene:
[1]. Ejecutar con join()
[2]. Ejecutar sin join()
Ingresar opción(1-2): 2
———————————–
Creación de hilo: Thread[Primero,5,main]
Creación de hilo: Thread[Segundo,5,main]
Creación de hilo: Thread[Tercero,5,main]
Hilo ejecutándose: Thread[main,5,main]
No hay llamada a join()
Finaliza hilo main()
Hilo: Thread[Primero,5,main] i = 6
Hilo: Thread[Tercero,5,main] i = 6
Hilo: Thread[Segundo,5,main] i = 6
Hilo: Thread[Segundo,5,main] i = 4
Hilo: Thread[Primero,5,main] i = 4
Hilo: Thread[Tercero,5,main] i = 4
Hilo: Thread[Primero,5,main] i = 2
Hilo: Thread[Segundo,5,main] i = 2
Hilo: Thread[Tercero,5,main] i = 2
Finaliza hilo: Segundo
Finaliza hilo: Primero
Finaliza hilo: Tercero
Observe que en esta ejecución termina el hilo del método main() antes de los tres hilos creados desde main(). Por contra, la ejecución con la opción 1 (llamada a join()) main() termina en último lugar. Esto se debe a que la llamada a join() desde el hilo principal provoca que este se quede a la espera de que termine la ejecución de cada uno de los tres hilos implementados en Tareas.