Saltar al contenido principal

Coordinación, interrupción y cancelación

Interrumpir no significa matar un hilo. interrupt() envía una señal cooperativa; la tarea debe observarla y terminar de forma ordenada.

Consultar la señal​

  • thread.isInterrupted() consulta el flag de ese hilo sin limpiarlo.
  • Thread.interrupted() consulta el flag del hilo actual y lo limpia.
Runnable tarea = () -> {
try {
while (!Thread.currentThread().isInterrupted()) {
procesarSiguienteElemento();
}
} finally {
liberarRecursos();
}
};

Está garantizado que interrupt() solicita la interrupción. No garantiza cuándo acabará la tarea: puede tardar en comprobar el flag o ignorarlo.

InterruptedException​

Operaciones bloqueantes como sleep, join o ciertos métodos de espera pueden lanzar InterruptedException. Normalmente esa operación limpia el flag al lanzar la excepción.

Si el método no puede propagarla y decide preservar la señal:

try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}

Un catch (InterruptedException e) {} vacío pierde una petición de cancelación y dificulta el cierre.

join() coordina terminación​

trabajador.start();
trabajador.join();
System.out.println("El trabajador terminó");

Después de join() sabemos que ese hilo terminó. Dos trabajadores iniciados antes de dos join() siguen sin tener orden relativo garantizado.

Timeout y cancelación de Future​

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
// trabajo por fragmentos
}
throw new InterruptedException("cancelada");
});

try {
System.out.println(future.get(500, TimeUnit.MILLISECONDS));
} catch (TimeoutException e) {
boolean aceptada = future.cancel(true);
System.out.println("Cancelación solicitada: " + aceptada);
} catch (InterruptedException e) {
future.cancel(true);
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
System.err.println(e.getCause().getMessage());
} finally {
executor.shutdown();
}

El timeout significa que get dejó de esperar; no que la tarea terminara. cancel(true) intenta cancelar y solicita interrupción si la tarea está ejecutándose y procede. No la mata ni garantiza terminación inmediata. Después de una cancelación efectiva, get() puede lanzar CancellationException.

Cierre ordenado​

La coordinación debe conservar recursos incluso ante fallos:

executor.shutdown();
try {
if (!executor.awaitTermination(2, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}

shutdownNow() también solicita interrupción; una tarea que la ignora puede continuar. Thread.stop(), suspend() y resume() son mecanismos obsoletos e inseguros y no deben utilizarse.

Experimento​

Ejecuta una tarea que procesa elementos y comprueba el flag entre elementos. Solicita interrupción desde main y repite.

  • Garantizado: se establece la solicitud de interrupción; join() espera la terminación.
  • Posible: la tarea puede procesar distinta cantidad antes de observarla.
  • Observado: anota cuántos elementos procesó cada ejecución, sin prometer esa cifra.

Práctica​

Corrige este bloque:

try {
cola.take();
} catch (InterruptedException e) {
}

Solución​

Si el método no puede propagar la excepción y debe terminar:

try {
cola.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}

Restauramos el flag para que niveles superiores puedan observar que el hilo fue interrumpido.

Errores habituales​

  • Describir interrupt() o cancel(true) como «matar».
  • Confundir isInterrupted() con Thread.interrupted().
  • Tragar InterruptedException.
  • Creer que un timeout detiene la tarea.
  • Olvidar el cierre del executor en caminos de error.

La siguiente unidad aplicará timeout y cancelación a procesos externos mediante ProcessBuilder.