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>
.
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étodorun()
. - Puede ser cancelada en cualquier momento, dado que implementa la interfaz
Future
, a través de su métodocancel(mayInterruptIfRunning)
- Permite consultar si la operación se ha completado, dado que implementa la interfaz
Future
, a través de su métodoisDone()
. - Podemos obtener el resultado de la operación, dado que implementa la interfaz
Future
, a través del métodoget()
, 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;
}
}