Skip to content

9 Métodos de envío de tareas en la interfaz ExecutorService

Métodos de envío de tareas en la interfaz ExecutorService

La interfaz ExecutorService, además de los métodos para la terminación de un ejecución, que hemos estudiado anteriormente, define una serie de métodos para el envío de tareas:

public interface ExecutorService extends Executor {

    // ...

    // Métodos relacionados con el envío de runnables.
    Future<?> submit(Runnable task);
    <T> Future<T> submit(Runnable task, T result);

    // Métodos relacionados con el envío de callables.
    <T> Future<T> submit(Callable<T> task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

}

Así, la interfaz ExecutorService proporciona nuevos métodos para enviar runnables a un ejecutor, pero que, a diferencia del método execute(runnable) de la interfaz Executor, retornan un objeto de la interfaz Future que representa la tarea asíncrona enviada al ejecutor.

El primero de ellos es el método submit(runnable), que envía al ejecutor la tarea correspondiente al runnable. Retorna un Future<?> que representa la tarea asíncrona que se está ejecutando, y que podemos usar para cancelar la tarea. El objeto Future no contendrá ningún valor.

Una mejora a este último es el método submit(runnable, V value), que envía al ejecutor la tarea correspondiente al runnable. Retorna un Future<T>, cuyo valor será establecido a value cuando el runnable termine de ejecutarse correctamente, sin que se haya producido una excepción o haya sido cancelada antes de llegar a comenzar su ejecución. Como vemos, nos permite establecer el valor del Future asociado a la tarea aunque ésta realmente no haya retornado dicho valor.

Aparte de las mejoras para el envío de runnables al ejecutor, la interfaz ExecutorService proporciona una serie de métodos específicos para el envío al ejecutor de tareas en forma de objetos Callable.

El primero de ellos es el método submit(callable<V>), que envía al ejecutor la tarea representada por el callable. Retorna un Future<T>, cuyo valor será el valor retornado por el método call() del callable.

Dentro de poco estudiaremos en su propio apartado los otros dos métodos disponibles en esta interfaz para el envío de listas de objetos Callable, llamados invokeAll(callableCollection) y invokeAny(callableCollection.

Executor Service

Figura - Executor Service

Proyecto Callable

En este proyecto desarrollaremos una aplicación que calcula el factorial de 10 números distintos. Crearemos un tarea Callable que calcule el factorial de un número y un ejecutor con un threadpool fijo de 2 hilos que lance las diez tareas y obtenga el resultado de cada una de ellas, usando objetos Future, y lo almacene en una lista, para finalmente mostrar todos los resultados.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

class Main {

    public static void main(String[] args) {
        ThreadPoolExecutor fixedThreadPool =
                (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
        List<Future<Integer>> futureList = new ArrayList<>();
        int[] numbers = new int[10];
        for (int i = 0; i < 10; i++) {
            numbers[i] = ThreadLocalRandom.current().nextInt(15) - 5;
            Task task = new Task(numbers[i]);
            futureList.add(fixedThreadPool.submit(task));
        }
        // Main thread can do some work here.
        // ...
        System.out.print("Results:\n");
        try {
            for (int i = 0; i < futureList.size(); i++) {
                Future<Integer> future = futureList.get(i);
                try {
                    Integer factorial = future.get();
                    System.out.printf("Task %d -> factorial(%d) = %d\n", i + 1,
                            numbers[i], factorial);
                } catch (ExecutionException e) {
                    System.out.printf("Task %d -> factorial(%d) threw an exception\n",
                            i + 1, numbers[i]);
                }
            }
        } catch (InterruptedException ignored) {
        } finally {
            fixedThreadPool.shutdown();
        }
    }

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

class Task implements Callable<Integer> {

    private final int number;

    Task(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.MILLISECONDS.sleep(20);
        }
        return factorial;
    }

}

Si ejecutamos el programa veremos cómo se utiliza la interfaz Future para acceder a los resultados de las tareas de cálculo enviadas al ejecutor.