¿Cuáles son las ventajas de usar un ExecutorService?

¿Cuál es la ventaja de utilizar ExecutorService sobre la ejecución de subprocesos pasando un Runnable al constructor Thread ?

ExecutorService abstrae muchas de las complejidades asociadas con las abstracciones de nivel inferior como Raw Thread . Proporciona mecanismos para iniciar, cerrar, enviar, ejecutar y bloquear de forma segura la terminación exitosa o abrupta de las tareas (expresadas como Runnable o Callable ).

De JCiP , Sección 6.2, directamente de la boca del caballo:

Executor puede ser una interfaz simple, pero forma la base de un marco flexible y potente para la ejecución de tareas asincrónicas que admite una amplia variedad de políticas de ejecución de tareas. Proporciona un medio estándar para desacoplar el envío de tareas desde la ejecución de tareas , describiendo las tareas como Runnable . Las implementaciones de Executor también brindan soporte de ciclo de vida y ganchos para agregar recostackción de estadísticas, administración de aplicaciones y monitoreo. … El uso de un Executor suele ser la ruta más fácil para implementar un diseño productor-consumidor en su aplicación.

En lugar de perder el tiempo implementando (a menudo incorrectamente y con gran esfuerzo) la infraestructura subyacente para el paralelismo, el marco de juconcurrent permite centrarse en tareas de estructuración, dependencias, paralelismo potencial. Para una gran cantidad de aplicaciones simultáneas, es fácil identificar y explotar los límites de las tareas y hacer uso de juc , lo que le permite centrarse en el subconjunto mucho más pequeño de los verdaderos desafíos de concurrencia que pueden requerir soluciones más especializadas.

Además, a pesar de la apariencia y el estilo repetidos, la página de la API de Oracle que resume las utilidades de simultaneidad incluye algunos argumentos realmente sólidos para usarlos, entre otros:

Es probable que los desarrolladores ya entiendan las clases de biblioteca estándar, por lo que no es necesario aprender la API y el comportamiento de los componentes concurrentes ad-hoc. Además, las aplicaciones concurrentes son mucho más simples de depurar cuando se basan en componentes confiables y bien probados.

Esta pregunta sobre SO pregunta acerca de un buen libro, cuya respuesta inmediata es JCiP. Si aún no lo has hecho, consígase una copia. El enfoque integral de concurrencia presentado va más allá de esta pregunta y le ahorrará mucho dolor en el largo plazo.

Una ventaja que veo es en la administración / progtwigción de varios hilos. Con ExecutorService, no tiene que escribir su propio administrador de hilos que puede estar plagado de errores. Esto es especialmente útil si su progtwig necesita ejecutar varios hilos a la vez. Por ejemplo, si desea ejecutar dos hilos a la vez, puede hacerlo fácilmente así:

 ExecutorService exec = Executors.newFixedThreadPool(2); exec.execute(new Runnable() { public void run() { System.out.println("Hello world"); } }); exec.shutdown(); 

El ejemplo puede ser trivial, pero trate de pensar que la línea “hello world” consiste en una operación pesada y desea que la operación se ejecute en varios hilos a la vez para mejorar el rendimiento de su progtwig. Este es solo un ejemplo, todavía hay muchos casos en los que desea progtwigr o ejecutar varios subprocesos y utilizar ExecutorService como su administrador de subprocesos.

Para ejecutar un solo hilo, no veo ninguna ventaja clara de usar ExecutorService.

A continuación hay algunos beneficios:

  1. El servicio Executor administra el hilo de manera asíncrona
  2. Use llamable para obtener el resultado de retorno después de completar el hilo.
  3. Administre la asignación de trabajo para el hilo libre y la reventa del trabajo completado desde el hilo para asignar el nuevo trabajo automáticamente
  4. fork – join framework para parallel processing
  5. Mejor comunicación entre hilos
  6. invokeAll y invokeAny dan más control para ejecutar cualquiera o todos los hilos a la vez
  7. shutdown proporciona la capacidad de completar todo el trabajo asignado por subprocesos
  8. Los servicios de ejecución progtwigda proporcionan métodos para producir invocaciones repetitivas de elementos ejecutables y callables. Espero que te ayude.

Las siguientes limitaciones del Thread tradicional han sido superadas por el marco Executor (marco incorporado de Thread Pool).

  • Mala gestión de recursos, es decir, sigue creando nuevos recursos para cada solicitud. Sin límite para crear recursos. Usando el marco Executor podemos reutilizar los recursos existentes y poner límite a la creación de recursos.
  • No es robusto : si seguimos creando un nuevo hilo obtendremos la excepción StackOverflowException por lo que nuestra JVM se bloqueará.
  • Sobrecarga Creación de tiempo : para cada solicitud necesitamos crear un nuevo recurso. Crear un nuevo recurso lleva mucho tiempo. es decir, Thread Creating> task. Usando el marco Executor podemos construirlo en Thread Pool.

Beneficios de Thread Pool

  • El uso del grupo de subprocesos reduce el tiempo de respuesta al evitar la creación de subprocesos durante la solicitud o el procesamiento de tareas.

  • El uso de Thread Pool le permite cambiar su política de ejecución según lo necesite. puede pasar de un solo hilo a un hilo múltiple simplemente reemplazando la implementación de ExecutorService.

  • Thread Pool en la aplicación Java aumenta la estabilidad del sistema al crear una cantidad configurada de hilos decididos en función de la carga del sistema y el recurso disponible.

  • Thread Pool libera al desarrollador de aplicaciones de las tareas de gestión de subprocesos y permite centrarse en la lógica empresarial.

Fuente

¿De verdad es tan caro crear un nuevo hilo?

Como punto de referencia, acabo de crear 60,000 hilos con Runnable con métodos run() vacíos. Después de crear cada hilo, llamé a su método de start(..) inmediatamente. Esto llevó unos 30 segundos de intensa actividad de CPU. Experimentos similares se han realizado en respuesta a esta pregunta . El resumen de ésto es que si los hilos no terminan inmediatamente, y se acumula una gran cantidad de hilos activos (algunos miles), entonces habrá problemas: (1) cada hilo tiene una stack, por lo que se quedará sin memoria , (2) podría haber un límite en el número de hilos por proceso impuesto por el SO, pero no necesariamente, al parecer .

Entonces, hasta donde puedo ver, si estamos hablando de lanzar 10 hilos por segundo, y todos terminan más rápido que los nuevos, y podemos garantizar que esta tasa no se excederá demasiado, entonces el ExecutorService no ofrece ninguna ventaja concreta en el rendimiento o la estabilidad visibles. (Aunque todavía puede hacer que sea más conveniente o legible express ciertas ideas de concurrencia en el código.) Por otro lado, si puede progtwigr cientos o miles de tareas por segundo, lo que lleva tiempo ejecutar, podría encontrarse con grandes problemas inmediatamente. Esto podría suceder inesperadamente, por ejemplo, si crea subprocesos en respuesta a solicitudes a un servidor, y hay un aumento en la intensidad de las solicitudes que recibe su servidor. Pero, por ejemplo, un hilo en respuesta a cada evento de entrada del usuario (pulsación de tecla, movimiento del mouse) parece estar perfectamente bien, siempre que las tareas sean breves.

ExecutorService también da acceso a FutureTask que devolverá a la clase llamante los resultados de una tarea en segundo plano una vez completada. En el caso de implementar Callable

 public class TaskOne implements Callable { @Override public String call() throws Exception { String message = "Task One here. . ."; return message; } } public class TaskTwo implements Callable { @Override public String call() throws Exception { String message = "Task Two here . . . "; return message; } } // from the calling class ExecutorService service = Executors.newFixedThreadPool(2); // set of Callable types Set>callables = new HashSet>(); // add tasks to Set callables.add(new TaskOne()); callables.add(new TaskTwo()); // list of Future types stores the result of invokeAll() List>futures = service.invokeAll(callables); // iterate through the list and print results from get(); for(Futurefuture : futures) { System.out.println(future.get()); } 

Antes de la versión 1.5 de java, Thread / Runnable se diseñó para dos servicios separados

  1. Unidad de trabajo
  2. Ejecución de esa unidad de trabajo

ExecutorService desacopla esos dos servicios designando Runnable / Callable como unidad de trabajo y Executor como un mecanismo para ejecutar (con lifecycling) la unidad de trabajo

Crear una gran cantidad de subprocesos sin restricción al umbral máximo puede hacer que la aplicación se agote de la memoria del montón. Por eso, crear ThreadPool es una solución mucho mejor. Usando ThreadPool podemos limitar el número de hilos que se pueden agrupar y reutilizar.

El marco de los ejecutores facilita el proceso de creación de grupos de subprocesos en Java. La clase de ejecutores proporciona una implementación simple de ExecutorService usando ThreadPoolExecutor.

Fuente:

Qué es el Marco de los Ejecutores