4 Terminación de un ejecutor¶
Terminación de un ejecutor¶
La interfaz ExecutorService
extiende la interfaz Executor
para proporcionar la funcionalidad que solvente las limitaciones de ésta última. Este aspecto es muy importante, ya que los ejecutores están diseñados de manera que si no se termina su ejecución explícitamente, el programa que lo uso nunca terminará, incluso aunque el ejecutor no tenga ninguna tarea para ejecutar, dado que los hilos del ejecutor seguirán vivos.
Por este motivo, la interfaz ExecutorService
proporciona, entre otras cosas, una serie de métodos que permitan a la aplicación terminar (detener) un ejecutor y sus hilos, liberando los recursos obtenidos.
Veamos parte de la definición de la interfaz ExecutorService
:
public interface ExecutorService extends Executor {
// Métodos relacionados con el control del ciclo de vida de los ejecutores
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// ...
}
Así, tenemos el método shutdown()
, que termina el ejecutor, de manera que ya no se aceptan más tareas nuevas en él, que son rechazadas, pero a las tareas que estuvieran ejecutándose o las que ya hubieran sido recibidas y aún no se hayan ejecutado se les permitirá concluir su ejecución. A este se le conoce como terminación "ordenada".
Pero si queremos llevar a cabo una terminación de ejecutor mucho más drástica, podemos llamar al método shutdownNow()
, que termina el ejecutor inmediatamente, de manera que las nuevas tareas que le lleguen son rechazadas y los hilos del threadpool en los que se están ejecutando tareas en ese momento son marcados para interrupción, aunque no hay garantía de que las tareas decidan detectar la interrupción y finalizar su ejecución. Las tareas que estuvieran en el ejecutor pendientes de comenzar su ejecución no llegarán a ser ejecutadas, y se retornará un lista List<Runnable>
con éstas. El método retorna inmediatamente, sin esperar a que las tareas que ya están en ejecución terminen de ejecutarse.
Si después de haber llamado al método shutdown()
o shutdownNow()
tratamos de enviar alguna otra tarea al ejecutor, ésta será rechazada y el ejecutor lanzará una excepción RejectedExecutionException
en la llamada al método que ha sido usado para el envío.
Si queremos esperar a la terminación del ejecutor, es decir a que todas las tareas que gestiona terminen de ejecutarse, podemos llamar al método awaitTermination(long timeout, TimeUnit unit)
, que bloquea el hilo en el que se realiza la llamada hasta que las tareas del ejecutor hayan terminado o transcurre el tiempo máximo de espera (timeout). Este método retorna un booleano indicativo de si realmente terminó el ejecutor. Si se sobrepasa el tiempo de espera máximo se retornará false
.
Debemos tener en cuenta que si llamamos a awaitTermination()
sin haber llamado antes a shutdown()
o shutdownNow()
, la llamada a awaitTermination()
retornará inmediatamente y no tendrá ningún efecto.
Una de las opciones recomendadas para terminar un ejecutor es usar una combinación de los métodos anteriores, tratando de finalizar el ejecutor de forma ordenada, esperando un tiempo prudencial a su terminación, transcurrido el cuál se fuerza la finalización:
executorService.shutdown();
try {
if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
Además tendremos disponibles otros métodos informativos relacionados con la finalización del ejecutor:
isShutdown()
: Retornatrue
si se ha llamado al métodoshutdown()
oshutdownNow()
del ejecutor.isTerminating()
: Retornatrue
si el ejecutor está realizando la operación de finalizar su ejecución, después de haber sido llamado el métodoshutdown)
oshutdownNow()
, pero no ha concluido aún.isTerminated()
: Retornatrue
si se ha llamado al métodoshutdown()
oshutdownNow()
del ejecutor y todas las tareas han finalizado su ejecución.