Java separa la definición de una tarea del mecanismo para ejecutarla. Con Runnable describes trabajo sin retorno, con Callable devuelves valores y con Future recuperas el resultado o cancelas la ejecución.
run() es void; ideal para tareas que solo producen efectos secundarios.call() devuelve un valor genérico T.ExecutionException al leer el resultado.import java.util.concurrent.*;
public class DemoCallableFuture {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newSingleThreadExecutor();
Callable<Integer> tarea = () -> {
TimeUnit.MILLISECONDS.sleep(200);
return 42;
};
Future<Integer> futuro = pool.submit(tarea);
if (!futuro.isDone()) {
System.out.println("Sigue en curso...");
}
try {
Integer resultado = futuro.get(); // bloquea hasta terminar
System.out.println("Resultado: " + resultado);
} catch (ExecutionException e) {
System.err.println("Fallo interno: " + e.getCause());
} finally {
pool.shutdown();
}
}
}
Callable o Runnable a un ExecutorService.get() (bloquea hasta tener el valor), isDone() (saber si terminó), cancel() (intenta detenerlo, opcionalmente interrumpiendo).get() envuelve errores en ExecutionException; revisa getCause() para el origen.get(timeout) evita bloqueos indefinidos; combinaciones como isDone() + get(0, TimeUnit.SECONDS) ayudan a consultar sin esperar.Ejemplo que dispara una excepción por timeout al leer el resultado de un Future; ilustra cómo reaccionar cuando la tarea tarda más de lo esperado.
import java.util.concurrent.*;
public class DemoCallableFuture {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newSingleThreadExecutor();
Callable<Integer> tarea = () -> {
TimeUnit.MILLISECONDS.sleep(2_000);
return 42;
};
Future<Integer> futuro = pool.submit(tarea);
if (!futuro.isDone()) {
System.out.println("Sigue en curso...");
}
try {
Integer resultado = futuro.get(1, TimeUnit.SECONDS); // lanza TimeoutException
System.out.println("Resultado: " + resultado);
} catch (TimeoutException e) {
System.err.println("Tardó demasiado: " + e);
futuro.cancel(true); // intenta interrumpir
} catch (ExecutionException e) {
System.err.println("Fallo interno: " + e.getCause());
} finally {
pool.shutdown();
}
}
}
La excepción surge porque get(1, TimeUnit.SECONDS) espera solo un segundo, mientras la tarea duerme dos segundos. Al excederse el límite se lanza TimeoutException y el código intenta cancelar el futuro con interrupción cooperativa.
Este patrón resulta útil cuando consultas un microservicio o base externa con SLO estricto: si la respuesta no llega en 1s, cancelas la tarea, devuelves un fallback o pasas al siguiente nodo sin bloquear el hilo consumidor.
get() bloqueante: si la tarea se queda colgada, el hilo consumidor también; usa timeouts.CompletableFuture (mencionado sin profundizar) ofrece encadenamiento, manejo asíncrono y mejor composición.