Java – como usar ExecutorService

Java ExecutorService

ExecutorService nos simplifica las tareas asincronas proveyendo un pool de hilos que son manejados por esta API abstrayendonos del trabajo de crearlos y asignarles tareas a estos hilos.

java executor service

Utilizando la factory class Executors

La forma más común para crear una instancia de ExecutorService es a través de la factory class Executors.

Por ejemplo si deseas crear pool con 10 threads lo puedes hacer de este modo:

ExecutorService executor = Executors.newFixedThreadPool(10);

Si quieres crear un pool con un solo hilo en los que las tareas queden en cola y se ejecuten de forma secuencial:

ExecutorService executor = Executors.newSingleThreadExecutor();

Creando instancias directamente

Puedes directamente crear instancias de cualquiera de las implementaciones de ExecutorService, observa que esta es una interfaz con múltiples implementaciones.

Por ejemplo para crear un pool de 10 hilos del mismo modo que lo hace la factory class Executors.newSingleThreadExecutor().

ExecutorService executor = new ThreadPoolExecutor(10, 10,
       0L, TimeUnit.MILLISECONDS,
       new LinkedBlockingQueue<Runnable>());

¿Cómo ejecutar tareas con ExecutorService?

Usando ExecutorService.execute

ExecutorService en su método execute ejecuta cualquier implementacion de Runnable, por lo que su ejecución la haces de este modo:

Runnable command = new Runnable() {
   @Override
   public void run() {
       doLongWork();
   }
};
executor.execute(command);

Usando lambdas puedes cambiar este código por uno más limpio. Copia este codigo completo y pruebalo localmente.

package executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceExample {

   public static void main(String[] args) {

       ExecutorService executor = Executors.newFixedThreadPool(10);
       executor.execute(() -> doLongWork("hi 1"));
       executor.execute(() -> doLongWork("hi 2"));
       executor.execute(() -> doLongWork("hi 3"));
   }

   private static void doLongWork(String hola) {
       System.out.println("Running " + hola);
       try {
           Thread.sleep(1000l);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }

}

Usando ExecutorService.submit

EjecutorService.execute es un método void por lo que no puedes conocer el resultado, pero ExecutorService puede también retornar el resultado de tu ejecución si utilizas el método submit.

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(() -> doLongWork("hi with future 1"));
future.get();

Future.get te devuelve el resultado de la ejecución de tu metodo luego de que este ya ha terminado.

Prueba este ejemplo completo

package executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceFutureExample {

   public static void main(String[] args) throws Exception {

       ExecutorService executor = Executors.newFixedThreadPool(10);
       Future<String> future = executor.submit(() -> doLongWork("hi with future 1"));
       System.out.println(future.get());
      
   }

   private static String doLongWork(String msg) {
       System.out.println("Running " + msg);
       try {
           Thread.sleep(1000l);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return "done " + msg;
   }

}

Usando ExecutorService.invokeAny

ExecutorService.invokeAny recibe una lista de Callable, ejecuta todas las task y devuelve el resultado de una ejecución correcta

ExecutorService executor = Executors.newFixedThreadPool(10);

Collection<Callable<String>> callables = new ArrayList<>();
callables.add(() -> doLongWork("hi! 1"));
callables.add(() -> doLongWork("hi! 2"));
String oneResult = executor.invokeAny(callables);

Usando ExecutorService.invokeAll

ExecutorService.invokeAll recibe una también una lista de Callable, ejecuta todas las task y pero devuelve el resultado de todas las ejecuciones

package executor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceFutureAnyExample {

   public static void main(String[] args) throws Exception {

       ExecutorService executor = Executors.newFixedThreadPool(10);

       Collection<Callable<String>> callables = new ArrayList<>();
       callables.add(() -> doLongWork("hi! 1"));
       callables.add(() -> doLongWork("hi! 2"));

       List<Future<String>> result = executor.invokeAll(callables);

       for (Future f : result) {
           System.out.println("f.isDone :  " + f.isDone() + " f.get : " + f.get());
       }

   }

   private static String doLongWork(String msg) {
       System.out.println("Running " + msg);
       try {
           Thread.sleep(1000l);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return "finish " + msg;
   }

}

Como terminar ExecutorService

Por defecto ExecutorService se queda ‘escuchando’ nuevas tareas, se queda vivo para recibir nuevas tareas y asignarlas a su pool. Si deseas finalizarlo para que no reciba nuevas tareas puedes ejecutar su método shutdown() para “solicitarle” la terminación. Una vez terminadas la tareas en ejecución se finalizará el servicio.

También puedes requerir la finalización inmediata usando shutdownNow(), aunque esto último no esta garantizado. Este método te devuelve la lista de tareas que no finalizaron si deseas conocerlas.

ExecutorService executor = Executors.newFixedThreadPool(10);
...
List<Runnable> runnableList = executor.shutdownNow();