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()ocancel(true)como «matar». - Confundir
isInterrupted()conThread.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.