Los threads (hilos) o también llamados subprocesos es una característica de Java que permite la ejecución simultánea de dos o más partes de un programa para la máxima utilización de la CPU. Cada parte de dicho programa se denomina thread (hilo). Entonces, los hilos son procesos livianos dentro de un proceso.
Vídeo explicativo
Ciclo de vida de un hilo (thread)
Un hilo pasa por varias etapas en su ciclo de vida. Por ejemplo, un subproceso nace, se inicia, se ejecuta y luego muere:
- Nuevo: un nuevo subproceso comienza su ciclo de vida en el nuevo estado. Permanece en este estado hasta que el programa inicia el hilo. También se conoce como hilo nacido.
- Ejecutable: después de que se inicia un subproceso recién nacido, el subproceso se vuelve ejecutable. Se considera que un subproceso en este estado está ejecutando su tarea.
- En espera: a veces, un subproceso pasa al estado de espera mientras espera a que otro subproceso realice una tarea. Un subproceso vuelve al estado ejecutable solo cuando otro subproceso indica al subproceso en espera que continúe ejecutándose.
- Espera cronometrada: un subproceso ejecutable puede entrar en el estado de espera cronometrada durante un intervalo de tiempo específico. Un subproceso en este estado vuelve al estado ejecutable cuando expira ese intervalo de tiempo o cuando ocurre el evento que está esperando.
- Terminado (muerto): un subproceso ejecutable ingresa al estado terminado cuando completa su tarea o termina de otra manera.
Prioridades de hilos
Cada subproceso de Java tiene una prioridad que ayuda al sistema operativo a determinar el orden en que se programan los subprocesos.
Las prioridades de subprocesos de Java están en el rango entre MIN_PRIORITY (una constante de 1) y MAX_PRIORITY (una constante de 10). De forma predeterminada, a cada subproceso se le da prioridad NORM_PRIORITY (una constante de 5).
Los subprocesos con mayor prioridad son más importantes para un programa y se les debe asignar tiempo de procesador antes que los subprocesos de menor prioridad. Sin embargo, las prioridades de los subprocesos no pueden garantizar el orden en que se ejecutan los subprocesos y dependen en gran medida de la plataforma.
Crear un hilo
Hay dos formas de crear un hilo el primero es crear una subclase de Thread y sobreescribir el método run(). La segunda forma es pasar un objeto que implementa Runnable (java.lang.Runnable).
Subclase de Thread
La primera forma de especificar qué código debe ejecutar un subproceso es crear una subclase de Subproceso y sobreescribir el método run(). El método run() es lo que ejecuta el subproceso después de llamar a start(). Aquí hay un ejemplo de cómo crear una subclase de hilo Java:
public class MiHilo extends Thread {
public void run(){
System.out.println("Corriendo hilo...");
}
}
Para crear y comenzar el hilo anterior, puede hacer lo siguiente:
MiHilo miHilo = new MiHilo();
miHilo.start();
La llamada start() regresará tan pronto como se inicie el hilo. No esperará hasta que finalice el método run(). El método run() se ejecutará como si lo ejecutara una CPU diferente. Cuando se ejecuta el método run(), imprimirá el texto “Corriendo hilo…“.
También puede crear una subclase anónima de Thread como esta:
Thread hiloAnonimo = new Thread(){
public void run(){
System.out.println("Corriendo hilo anonimo...");
}
}
hiloAnonimo.start();
Interfaz Runnable
La segunda forma de especificar qué código debe ejecutar un subproceso es creando una clase que implemente la interfaz java.lang.Runnable. Un objeto Java que implementa la interfaz Runnable puede ser ejecutado por un subproceso Java. Cómo se hace eso se muestra un poco más adelante en este tutorial.
Implementando Runnable
La primera forma de implementar la interfaz Java Runnable es creando su propia clase Java que implemente la interfaz Runnable. Aquí hay un ejemplo de una clase Java personalizada que implementa la interfaz Runnable:
public class MiHilo implements Runnable {
public void run(){
System.out.println("Mi hilo esta corriendo...");
}
}
Todo lo que hace esta implementación de Runnable es imprimir el texto “Mi hilo esta corriendo…“. Después de imprimir ese texto, el método run() sale y el subproceso que ejecuta el método run() se detendrá.
Implementación anónima
También puede crear una implementación anónima de Runnable. Aquí hay un ejemplo de una clase Java anónima que implementa la interfaz Runnable:
Runnable miHilo = new Runnable(){
public void run(){
System.out.println("Mi hilo anonimo esta corriendo");
}
};
Implementación Lambda
La tercera forma de implementar la interfaz Runnable es creando una implementación Java Lambda de la interfaz Runnable. Esto es posible porque la interfaz Runnable solo tiene un único método no implementado y, por lo tanto, es prácticamente una interfaz Java funcional.
Aquí hay un ejemplo de una expresión lambda de Java que implementa la interfaz Runnable:
Runnable runnable = () -> { System.out.println("Corriendo Lambda Runnable..."); };
Iniciar un hilo con un ejecutable
Para que un subproceso ejecute el método run(), pase una instancia de una clase, una clase anónima o una expresión lambda que implemente la interfaz Runnable a un subproceso en su constructor. Así es como se hace:
Runnable runnable = new MiHilo(); // o una clase anónima, o lambda...
Thread thread = new Thread(runnable);
thread.start();
Error común: llamar a run() en lugar de start()
Al crear e iniciar un subproceso, un error común es llamar al método run() del subproceso en lugar de start(), así:
Thread miHilo = new Thread(MiHilo());
miHilo.run(); //debería ser start();
Al principio puede que no notes nada porque el método run() de Runnable se ejecuta como esperabas. Sin embargo, NO es ejecutado por el nuevo hilo que acabas de crear. En cambio, el método run() es ejecutado por el proceos principal o el subproceso que creó el subproceso. En otras palabras, se ejecutará cómo cualquier otra llamada a un método. Para que el nuevo subproceso creado, miHilo, llame al método run() de la instancia MiHilo, DEBE llamar al método miHilo.start().
Métodos de hilos
- public void start(): Inicia el subproceso en una ruta de ejecución separada, luego invoca el método run() en este objeto Subproceso.
- public void run(): Si se creó una instancia de este objeto Thread utilizando un destino Runnable separado, el método run() se invoca en ese objeto Runnable.
- public final void setName(String name): Cambia el nombre del objeto Thread. También hay un método getName() para recuperar el nombre.
- public final void setPriority(int priority): Establece la prioridad de este objeto Thread. Los valores posibles están entre 1 y 10.
- public final void setDaemon(boolean on): Un parámetro de true denota este subproceso como un subproceso daemon.
- public final void join(long millisec): El subproceso actual invoca este método en un segundo subproceso, lo que hace que el subproceso actual se bloquee hasta que finalice el segundo subproceso o pase el número especificado de milisegundos.
- public void interrupt(): Interrumpe este subproceso, lo que hace que continúe su ejecución si se bloqueó por algún motivo.
- public final boolean isAlive(): Devuelve true si el subproceso está vivo, que es cualquier momento después de que se haya iniciado el subproceso pero antes de que se complete.