Para usar un ExecutorService en Java tendremos dos formas de hacerlo.
- Por suerte, ya existen implementaciones de
ExecutorServicey deScheduledExecutorService, así que no tendremos que hacerla desde cero. Por ejemplo,ThreadPoolExecutorServicees un tipo deExecutorServiceque utiliza un pool de hilos (de esto hablo en la lección siguiente). - Sin embargo, una forma más simple de usar un
ExecutorServiceva a ser emplear los métodos de la claseExecutors. Estos métodos permiten instanciar rápidamente un tipo deExecutorServiceen función de las necesidades que tengamos.
Por ejemplo, puedo usar el siguiente código Java para obtener un ExecutorService:
ExecutorService svc = Executors.newSingleThreadExecutor();
En este caso, he pedido un single thread executor. Un executor de este modo sólo usará un único hilo, así que todas las tareas que encolemos mediante el método submit se harán una tras otra. A modo de ejemplo, podríamos encolar un grupo de tareas:
Ahora podríamos encolar estas tareas mediante el método submit:
for (int i = 0; i < 5; i++) {
final String taskId = "Tarea" + i;
svc.submit(() -> {
System.out.println(taskId + ": Ejecutando");
try {
Thread.sleep(2000);
System.out.println(taskId + ": Ejecutada");
} catch (InterruptedException e) {
System.out.println(taskId + ": Interrumpida");
}
});
}
Cuando se obtiene un ExecutorService, está listo para aceptar tareas inmediatamente, por lo que estas comenzarán a ejecutarse. Así que invocar el programa sin agregar código adicional, provocará que las tareas se ejecuten:
Tarea0: Ejecutando
Tarea0: Ejecutada
Tarea1: Ejecutando
Tarea1: Ejecutada
Tarea2: Ejecutando
Tarea2: Ejecutada
Tarea3: Ejecutando
Tarea3: Ejecutada
Tarea4: Ejecutando
Tarea4: Ejecutada
En este caso, notamos varias cosas:
- El orden de ejecución de las tareas va a ser secuencial. Hasta que una no termina, no empieza la siguiente. Además, las tareas parece que van en orden. Aunque no se puede garantizar esto, tiene sentido que se ejecuten en orden ya que se usa un único hilo para ejecutar tareas, por lo que el resultado es bastante secuencial.
- Notarás que el programa no termina. Esto es porque instanciar un
ExecutorServicepodría provocar que se fabriquen hilos, por lo que cuando el hilo principal termine, la máquina se quedará en ejecución debido a que hay otros hilos que continúan ejecutándose: los del propio ExecutorService.
Para impedir que el programa se quede pensando infinitamente, se debe utilizar el método shutdown() cuando ya no vayamos a enviarle más tareas:
svc.shutdown();
Cuando hagas eso, y opcionalmente esperes mediante awaitTermination() a que termine todo lo que tiene abierto, notarás que ya no se queda el programa infinitamente en ejecución luego de terminar el hilo principal y la última tarea que ahora mismo se esté ejecutando.
Estrategias de ExecutorService
Cuando usamos los métodos de la clase Executors, puede que no tengamos mucha información sobre cómo se están ejecutando esas tareas que encolemos, pero una de las estrategias más habituales que nos vamos a encontrar va a ser la estrategia ThreadPool. Detallaré en la siguiente lección qué es un thread pool.
La cuestión aquí es que en muchos de estos casos, los hilos que se generen por usar un ExecutorService se considerarán workers (trabajadores). Son hilos reusables que siempre están tratando de ejecutar código. Un hilo que actúe como worker no se crea ni se destruye con tanta frecuencia y eso permite ahorrar recursos.
Por lo tanto, por ir adquiriendo vocabulario técnico, podríamos empezar a decir que cuando usamos un ExecutorService, tenemos un servicio en el que los workers empiezan a aceptar tasks.