Cómo cerrar correctamente el ExecutorService de Java

Tengo un ExecutorService Java simple que ejecuta algunos objetos de tareas (implements Callable ).

 ExecutorService exec = Executors.newSingleThreadExecutor(); List tasks = new ArrayList(); // ... create some tasks for (CallableTask task : tasks) { Future future = exec.submit(task); result = (String) future.get(timeout, TimeUnit.SECONDS); // TASKS load some classes and invoke their methods (they may create additional threads) // ... catch interruptions and timeouts } exec.shutdownNow(); 

Una vez finalizadas todas las tareas (ya sea DONE o TIMEOUT-ed), bash apagar el ejecutor, pero no se detiene: exec.isTerminated() = FALSE. Sospecho que algunas tareas que están progtwigdas no se terminan correctamente.

Y sí, sé que el cierre del ejecutor no garantiza nada:

No hay garantías más allá de los bashs de mejor esfuerzo para detener el procesamiento de tareas de ejecución activa. Por ejemplo, las implementaciones típicas se cancelarán a través de {@link Thread # interrupt}, por lo que cualquier tarea que no responda a las interrupciones puede que nunca termine.

Mi pregunta es, ¿hay alguna manera de asegurar que esos hilos (tarea) terminen? La mejor solución que se me ocurrió fue llamar a System.exit() al final de mi progtwig, pero eso es una tontería.

Forma recomendada desde la página de documentación de API de Oracle de ExecutorService :

  void shutdownAndAwaitTermination(ExecutorService pool) { pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!pool.awaitTermination(60, TimeUnit.SECONDS)) System.err.println("Pool did not terminate"); } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); } 

Si su grupo toma más tiempo para el apagado, puede cambiar

 1f (!pool.awaitTermination(60, TimeUnit.SECONDS)) 

a

 while (!pool.awaitTermination(60, TimeUnit.SECONDS)) 

Un breve resumen de los métodos relacionados con el apagado

shutdown () :

Inicia un cierre ordenado en el que se ejecutan las tareas enviadas anteriormente, pero no se aceptarán nuevas tareas.

shutdownNow () :

Intenta detener todas las tareas de ejecución activa, detiene el procesamiento de las tareas en espera y devuelve una lista de las tareas que estaban esperando su ejecución.

awaitTermination (tiempo de espera largo, unidad TimeUnit) lanza InterruptedException :

Bloquea hasta que todas las tareas hayan completado la ejecución después de una solicitud de cierre, o se agote el tiempo de espera, o se interrumpa el hilo actual, lo que ocurra primero.

¿Tienes el control de esas tareas? es decir, ¿estás creando esos tú mismo? Sospecho que en algún lugar de esos se está ignorando una interrupción de hilo, por ejemplo

 try { .... } catch {InterruptedException e) { // do nothing } 

Cuando se lanza una excepción InterruptedException, el indicador de interrupción en el hilo debe reiniciarse; de ​​lo contrario, el hilo no saldrá. Mira aquí para más información .

Lamentablemente, puede estar utilizando una biblioteca que no obedece esto, en cuyo caso no puede eludir esto fácilmente. En ese caso, una opción de peso pesado es Callable un subproceso para realizar el trabajo del Callable , y eso borrará todos los recursos al salir del proceso. Pesado y posiblemente no trivial, pero confiable.