Skip to content

19 FutureTask

FutureTask

La clase FutureTask permite encapsular en un mismo objeto las funcionalidades de operación ejecutable y de un resultado futuro, es decir, de un Runnable y de un Future. De hecho, esta clase implementa la interfaz RunnableFuture<V>, que a su vez extiende de las interfaces Runnable y Future<V>.

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

Representa por tanto una operación asíncrona con una serie de características muy importantes:

  • Se puede ejecutar directamente en un hilo gestionado por nosotros mismos, o puede ser enviada a un ejecutor para que la ejecute, dado que implementa la interfaz Runnable, y por tanto dispone del método run().
  • Puede ser cancelada en cualquier momento, dado que implementa la interfaz Future, a través de su método cancel(mayInterruptIfRunning)
  • Permite consultar si la operación se ha completado, dado que implementa la interfaz Future, a través de su método isDone().
  • Podemos obtener el resultado de la operación, dado que implementa la interfaz Future, a través del método get(), que es bloqueante.

La clase FutureTask nos permite envolver un Callable que queramos ejecutar, que es recibido a través del constructor. El método run() del FutureTask llamará al método call() del Callable que envuelve, y establecerá el valor resultante de la llamada como valor del propio FutureTask.

Existe otra opción, consistente en pasar al constructor de la clase FutureTask un runnable junto a un valor de retorno. Internamente se creará un Callable cuyo método call() consiste en llamar al método run()del runnable y retornar el valor indicado. El método run() del FutureTask llamará al método call() del Callable creado, y establecerá el valor resultante de la llamada como valor del propio FutureTask.

Además de toda la funcionalidad proporcionada por las interfaces Runnable y Future, la clase FutureTask proporciona un método llamado done(), que es llamado internamente cuando el FutureTask se da por completado, estos es, cuando isDone() pasa a ser true, independientemente de si la tarea ha sido cancelada, la llamada al método call() ha finalizado normalmente y se ha establecido el valor retornado como valor del propio FutureTask, o el método call() del callable ha lanzado una excepción.

Podemos sobrescribir este método done(), permitiéndonos ejecutar código una vez haya terminado de ejecutarse la tarea. Podemos usar esta característica para realizar operaciones de post-procesamiento, generar un informe, enviar resultados por e-mail, o liberar algún recurso que estuviéramos utilizando, etc., aunque no podremos cambiar el valor retornado o el estado de la tarea. Si desde el método done() queremos acceder al valor retornado por la tarea podemos usar el método get() del FutureTask.

Por ejemplo, la clase ExecutorCompletionService hace uso de una extensión de la clase FutureTask, llamada ExecutorCompletionService.QueueingFuture, que sobrescribe el método done() para que cada Future ejecutado por el ejecutor asociado al ExecutorCompletionService cuando termine su ejecución se auto encole en la cola de tareas completadas.

Proyecto FutureTask

En este proyecto crearemos dos FutureTask que envuelve dos Callable que calculan dos factoriales distintos. La primera FutureTask será ejecutada a través de un hilo que crearemos expresamente para ello y la segunda FutureTask será enviada a un ejecutor.

Las FutureTask están diseñadas para que muestren por pantalla cuando se han completado si ha sido cancelada, ha finalizado el cálculo correctamente o se ha producido una excepción.

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        final DateTimeFormatter dateTimeFormatter =
                DateTimeFormatter.ofPattern("HH:mm:ss");
        ExecutorService executor = Executors.newCachedThreadPool();
        LogFutureTask<Integer> task1 = new LogFutureTask<>(new FactorialTask(-1));
        LogFutureTask<Integer> task2 = new LogFutureTask<>(new FactorialTask(4));
        Thread thread = new Thread(task1);
        System.out.printf("%s - %s - Task1 started\n",
                Thread.currentThread().getName(),
                dateTimeFormatter.format(LocalTime.now()));
        thread.start();
        System.out.printf("%s - %s - Task2 sent\n",
                Thread.currentThread().getName(),
                dateTimeFormatter.format(LocalTime.now()));
        executor.submit(task2);
        executor.shutdown();
        TimeUnit.SECONDS.sleep(2);
        //task1.cancel(true);
        //task2.cancel(true);
        thread.join();
        executor.awaitTermination(1, TimeUnit.MINUTES);
    }

}
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class LogFutureTask<V> extends FutureTask<V> {

    private final DateTimeFormatter dateTimeFormatter =
            DateTimeFormatter.ofPattern("HH:mm:ss");

    LogFutureTask(Callable<V> callable) {
        super(callable);
    }

    @Override
    protected void done() {
        if (isCancelled()) {
            System.out.printf("%s - %s - Operation cancelled\n",
                    Thread.currentThread().getName(),
                    dateTimeFormatter.format(LocalTime.now()));
        } else {
            try {
                V value = get();
                System.out.printf("%s - %s - Result: %s\n",
                        Thread.currentThread().getName(),
                        dateTimeFormatter.format(LocalTime.now()),
                        value);
            } catch (InterruptedException ignored) {
            } catch (ExecutionException e) {
                System.out.printf("%s - %s - %s\n",
                        Thread.currentThread().getName(),
                        dateTimeFormatter.format(LocalTime.now()),
                        e.getClass().getSimpleName());
            }
        }
    }

}
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

class FactorialTask implements Callable<Integer> {

    private final int number;

    FactorialTask(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws InterruptedException {
        return factorial(number);
    }

    private Integer factorial(int number) throws InterruptedException {
        if (number < 0) throw new IllegalArgumentException("Number can't be negative");
        int factorial = 1;
        for (int i = 2; i <= number; i++) {
            factorial *= i;
            TimeUnit.SECONDS.sleep(1);
        }
        return factorial;
    }
}