Cuando trabajamos con tareas asíncronas en Java, una herramienta fundamental para controlar esas tareas es la clase Future. Al enviar una tarea a un ExecutorService mediante el método submit, obtenemos un objeto Future que nos permite gestionar esa tarea de forma más cómoda y segura. Este objeto nos da la posibilidad de recuperar el resultado final, cancelar la tarea o consultar su estado.
Imaginemos que tenemos un ExecutorService configurado con un solo hilo para facilitar el control. Enviamos tareas que implementan Callable, que es similar a un Runnable pero con la capacidad de devolver un resultado. Por ejemplo, si nuestro Callable devuelve un Integer, el Future que recibimos será de tipo Future<Integer>. Podemos almacenar este Future en una variable para luego interactuar con la tarea.
Una de las funcionalidades más útiles es obtener el resultado de la tarea con el método get(). Este método bloquea el hilo que lo llama hasta que la tarea haya terminado y el resultado esté disponible, funcionando de manera similar a un await en otros lenguajes. Por ejemplo, si tenemos:
Future<Integer> futuro1 = executor.submit(miCallable);
Integer resultado = futuro1.get();
System.out.println("Resultado: " + resultado);
Aquí, el hilo principal esperará hasta que miCallable termine y devuelva un valor, que luego imprimiremos. Es importante manejar las excepciones que get() puede lanzar, como InterruptedException si el hilo es interrumpido mientras espera, o ExecutionException si la tarea terminó con una excepción.
Además, podemos consultar el estado de la tarea con métodos como isDone() para saber si ha finalizado o isCancelled() para verificar si fue cancelada. Esto nos permite evitar bloqueos innecesarios y tomar decisiones según el estado actual.
Si intentamos obtener el resultado con get() en un orden diferente al que se completan las tareas, el hilo se bloqueará hasta que la tarea solicitada termine. Por ejemplo, si tenemos dos tareas y llamamos primero a futuro2.get() cuando futuro1 termina antes, el hilo esperará a que futuro2 finalice, lo que puede afectar el flujo de ejecución.
Existe también el método getNow(), que intenta obtener el resultado sin esperar. Sin embargo, si la tarea aún no ha terminado, lanzará una excepción, por lo que debemos usarlo con precaución.
Otra característica clave es la capacidad de cancelar tareas con el método cancel(boolean mayInterruptIfRunning). Si la tarea aún no ha comenzado, la cancelación siempre tendrá éxito y la tarea no se ejecutará. Pero si la tarea ya está en ejecución, el comportamiento depende del parámetro mayInterruptIfRunning.
Si pasamos false, la tarea seguirá ejecutándose hasta completarse, ya que no se envía ninguna señal para detenerla. En cambio, si pasamos true, se intenta interrumpir la tarea en ejecución enviándole una señal de interrupción. Esto puede provocar que la tarea lance una InterruptedException y se detenga antes de terminar normalmente.
Por ejemplo, si cancelamos una tarea en ejecución con cancel(true), el hilo que ejecuta la tarea recibirá la interrupción y la tarea puede finalizar prematuramente. Pero si usamos cancel(false), la tarea continuará hasta completarse, ignorando la solicitud de cancelación.
Es importante tener en cuenta que si el hilo principal muere sin llamar a shutdown() en el ExecutorService, las tareas en ejecución seguirán corriendo en segundo plano, lo que puede hacer que el programa no termine correctamente. Por eso, siempre debemos asegurarnos de cerrar el ExecutorService cuando ya no lo necesitemos.
En resumen, Future nos ofrece un control muy potente sobre las tareas asíncronas en Java, permitiéndonos obtener resultados, cancelar tareas y consultar su estado de forma sencilla y segura. Este mecanismo se asemeja bastante al concepto de promesas en otros lenguajes, facilitando la programación concurrente y asíncrona.