Una de las operaciones que se puede hacer sobre un hilo es interrumpirlo.
Interrumpir un hilo consiste en mandarle de forma primitiva una señal, que puede ser interceptada por el hilo. Es una señal muy primitiva que no tiene parámetro y que sólo debería valer para una cosa. Fundamentalmente: hacer que el hilo deje de hacer lo que inicialmente estaba haciendo, y empiece a hacer otra cosa.
Nadie define qué es esa otra cosa porque no está definido. Sin embargo, lo normal es que interrumpas un hilo como forma de pedirle que deje de hacer lo que está haciendo y aborte su ejecución, antes de lo normal y sin completar la tarea por la que se inició el hilo en primer lugar. Por ejemplo, un hilo podría hacer una petición de red mediante protocolo HTTP a un servicio web para obtener datos externos, y si se recibe una interrupción, el hilo podría detener esa petición para que no concluya ni finalice con la captura de datos.
Sin embargo, esto es únicamente una recomendación, aunque si vas a usar de formas creativas las interrupciones de un hilo, convendría que lo documentases para no sorprender a nadie. Aquí un ejemplo: un hilo podría interceptar una interrupción para imprimir información de depuración mediante System.out.println() a fin de poder inspeccionar desde la consola del IDE qué está haciendo el hilo.
Activar una interrupción
La primera parte de uso de interrupciones es el envío de la interrupción. Y para ello es muy fácil. Una vez tengamos una referencia a un Thread que queramos interrumpir, podemos llamar al método interrupt(). Con este método, pueden pasar varias cosas.
Por lo general, interrumpir un hilo provoca que se active el flag de interrupción de un hilo. Puedes imaginarlo como un booleano que dice si el hilo ha sido interrumpido o no. Dicho de otro modo, si interrumpes un hilo, pones ese flag a true.
Sin embargo, como cuento un poco más abajo, en algunas situaciones puede que esto no sea el caso y que pase otra cosa. Pero antes, vamos a verlo por el otro lado y detectar esa interrupción desde el hilo interrumpido.
Detectar y tratar una interrupción
Desde el otro lado, vas a poder consultar si el hilo ha sido interrumpido mediante una llamada al método isInterrupted(), también de la clase Thread. Esto significa que si tienes una referencia al Thread actual, podrías usar este método para consultar si tu hilo está interrumpido o no. Simplemente: si devuelve true, es que el hilo ha sido interrumpido; si se devuelve false, es que no ha sido interrumpido.
Eso quiere decir que en condiciones normales, podrías educadamente consultar si el hilo ha sido interrumpido para poder parar de forma controlada su ejecución. Te pongo el siguiente ejemplo, donde un hilo ejecuta un bucle en el que tras cada iteración miro si hay que parar la ejecución.
public class Procesador extends Thread {
private Colaborador colab;
public Procesador(Colaborador colab) {
this.colab = colab;
}
@Override
public void run() {
for (int i =0; i < colab.partes(); i++) {
// Me lo invento.
var parte = colab.getParte(i);
var result = parte.procesar();
colab.enviar(result);
if (isInterrupted()) {
colab.limpiar(); // Ejemplo de cleanup
return; // Termina la ejecución de la función
}
}
colab.confirmar(); // Si llegamos aquí, es que todo se ha procesado.
}
}
En este caso ha sido más fácil porque nuestro hilo es una subclase de Thread. Sin embargo, si no tenemos una referencia al Thread para poder consultar si se ha interrumpido, podemos usar el método estático Thread.currentThread(). Este método de Java siempre devuelve una referencia al hilo actual en ejecución. El retorno de este método, por lo tanto, será un hilo, pero dependerá mucho de dónde se esté ejecutando. Por ejemplo, si llamas a Thread.currentThread() desde el hilo principal, te devolverá el hilo principal; pero si lo haces desde un Runnable, te devolverá el hilo al cual le hayas acoplado ese Runnable tras su ejecución.
var hilo = Thread.currentThread();
System.out.println("El ID de este hilo es: " + hilo.getName());
Además, otra cosa que hay que considerar es que isInterrupted() no limpia el flag de interrupción. Por lo tanto, en futuras llamadas a isInterrupted() seguirá devolviendo true. Si no vamos a detener la ejecución del hilo tras una interrupción, probablemente nos interese limpiar ese flag para volver a pasarlo a false.
Esto es algo que podemos hacer con el método estático Thread.interrupted(). Quitando el hecho de que es estático, este método también te dice si el hilo actual ha sido interrumpido. Recuerda: el concepto de hilo actual depende de qué hilo sea el que esté llamando a Thread.interrupted(). Este método también devuelve verdadero o falso según si previamente el flag de interrupción estaba puesto a true, pero la clave es que este método sí que se asegura de que, si previamente el hilo fue interrumpido, se limpie su indicador de interrupción. En otras palabras, después de llamar a Thread.interrupted(), devuelva verdadero o falso, el estado de interrupción del hilo volverá a ser falso hasta que vuelva a ser interrumpido una vez más. Si pretendes crear un sistema reusable de interrupciones donde interrumpir un hilo sirva para hacer una operación de gestión interna antes de continuar su ejecución, tal vez este sea el método que debas usar.
InterruptedException
Algunos métodos del lenguaje de programación Java van a alterar la forma en la que funciona el sistema de interrupciones.
Por ejemplo, el método Thread.sleep() es un método estático de Java que sirve para pausar la ejecución de un hilo durante un tiempo determinado, especificado en milisegundos o directamente como una instancia de tipo Duration para mayor comodidad a la hora de escribir y leer el código. Si llamas a Thread.sleep(5000) o a Thread.sleep(Duration.ofSeconds(5)), el hilo que ejecute ese método esperará 5 segundos. Durante 5 segundos, la función no devolverá, ni avanzará el flujo.
Para no malgastar tiempo de CPU, este método es uno de los muchos que cambia el estado de ejecución del hilo, desde RUNNING a WAITING. En otras palabras, si usas Thread.sleep para pausar la ejecución del hilo durante 5 minutos, el hilo dejará de estar ejecutándose. Al fin y al cabo, si tiene que esperar, es absurdo darle tiempo de CPU cuando el ordenador sabe con precisión cuándo tiene que terminar la llamada a sleep().
Sin embargo, ¿qué pasa si interrumpes un hilo que actualmente está esperando a que termine un sleep? Lo que ocurrirá es que sleep tratará la interrupción por ti. Y para que sepas que ese sleep ha durado menos de lo normal debido a que se ha interrumpido tu hilo, lanzará una excepción de tipo InterruptedException, que deberás tratar en tu programa (ya que es una excepción de tipo checked).
Además, ten en cuenta que cuando Java hace esto, limpia el estado de interrupción, así que no puedes esperar que isInterrupted() continúe devolviendo true luego de recibir un InterruptedException, salvo que se haya vuelto a interrumpir una segunda vez.
Thread.sleep no es el único método que hace esto. Otros métodos que se pueden ver afectados por interrupciones en momentos poco oportunos también pueden lanzar un InterruptedException si se interrumpen durante el proceso. En la siguiente lección, por ejemplo, veremos como join() es otro de estos métodos que puede lanzar una InterruptedException.