hilos - Tenedor de Java/Join vs ExecutorService-cuándo usar cuál?
thread pool java (6)
Acabo de terminar de leer esta publicación: ¿Cuál es la ventaja de un Java-5 ThreadPoolExecutor sobre un Java-7 ForkJoinPool? y sintió que la respuesta no es lo suficientemente recta.
¿Puede explicar en un lenguaje sencillo y ejemplos, cuáles son las compensaciones entre el marco Fork-Join de Java 7 y las soluciones más antiguas?
También leí el hit # 1 de Google sobre el tema Consejo de Java: cuándo usar ForkJoinPool vs ExecutorService de javaworld.com pero el artículo no responde la pregunta del título cuando , habla sobre las diferencias de API principalmente ...
Brian Goetz describe la situación mejor: https://www.ibm.com/developerworks/library/j-jtp11137/index.html
El uso de grupos de hilos convencionales para implementar fork-join también es un desafío porque las tareas fork-join pasan la mayor parte de sus vidas esperando otras tareas. Este comportamiento es una receta para el punto muerto de inanición de subprocesos, a menos que los parámetros se elijan cuidadosamente para vincular el número de tareas creadas o el grupo en sí no tiene límites. Los grupos de subprocesos convencionales están diseñados para tareas que son independientes entre sí y también se diseñan teniendo en cuenta las tareas potencialmente bloqueantes y de grano grueso: las soluciones de ensamblaje de horquillas no producen ninguno.
Recomiendo leer toda la publicación, ya que tiene un buen ejemplo de por qué querría usar un grupo de unión de horquilla. Fue escrito antes de que ForkJoinPool se convirtiera en oficial, por lo que el método coInvoke()
que se refiere se convirtió en invokeAll()
.
El marco Fork-Join es una extensión del marco Executor para abordar en particular los problemas de ''espera'' en programas recursivos de subprocesos múltiples. De hecho, las nuevas clases de framework Fork-Join se extienden desde las clases existentes del framework Executor.
Hay 2 características centrales en el marco Fork-Join
- Robo de trabajo (un hilo inactivo roba trabajo de un hilo que tiene tareas en cola más de lo que puede procesar actualmente)
- Posibilidad de descomponer recursivamente las tareas y recoger los resultados. (Aparentemente, este requisito debe haber surgido junto con la concepción de la noción de procesamiento paralelo ... pero carecía de un marco de implementación sólido en Java hasta Java 7)
Si las necesidades de procesamiento paralelo son estrictamente recursivas, no hay más remedio que ir a Fork-Join, de lo contrario debería funcionar el ejecutor o el marco Fork-Join, aunque se puede decir que Fork-Join utiliza mejor los recursos debido a los hilos inactivos ''robar'' algunas tareas de hilos más ocupados.
Fork Join es una implementación de ExecuterService. La principal diferencia es que esta implementación crea el grupo de trabajadores DEQUE. Donde la tarea se inserta desde un lado pero se retira de cualquier lado. Significa que si ha creado new ForkJoinPool()
buscará la CPU disponible y creará esa new ForkJoinPool()
trabajo. Luego distribuye la carga de manera uniforme en cada hilo. Pero si un hilo está trabajando lentamente y otros son rápidos, seleccionarán la tarea del hilo lento. desde la parte trasera. Los pasos a continuación ilustrarán mejor el robo.
Etapa 1 (inicialmente):
W1 -> 5,4,3,2,1
W2 -> 10,9,8,7,6
Etapa 2:
W1 -> 5,4
W2 -> 10,9,8,7,
Etapa 3:
W1 -> 10,5,4
W2 -> 9,8,7,
Mientras que el servicio Executor crea el número solicitado de subprocesos y aplica una cola de bloqueo para almacenar toda la tarea de espera restante. Si usó cachedExecuterService, creará un solo hilo para cada trabajo y no habrá cola de espera.
Fork-join es particularmente bueno para problemas recursivos , donde una tarea implica ejecutar subtareas y luego procesar sus resultados. (Esto se llama típicamente "divide y vencerás" ... pero eso no revela las características esenciales).
Si intentas resolver un problema recursivo como este usando un enhebrado convencional (por ejemplo, a través de un ExecutorService) terminas con hilos atados esperando que otros hilos te entreguen resultados.
Por otro lado, si el problema no tiene esas características, no hay un beneficio real del uso de fork-join.
Aquí hay un artículo de "Consejos de Java" que entra en más detalle:
Fork-join le permite ejecutar fácilmente trabajos de dividir y conquistar, que deben implementarse manualmente si desea ejecutarlo en ExecutorService. En la práctica, ExecutorService suele utilizarse para procesar muchas solicitudes independientes (también conocido como transacción) al mismo tiempo, y fork-join cuando quiere acelerar un trabajo coherente.
Java 8 proporciona una API más en los ejecutores
static ExecutorService newWorkStealingPool()
Crea un grupo de subprocesos de robo de trabajo utilizando todos los procesadores disponibles como su nivel de paralelismo de destino.
Con la adición de esta API, los Executors proporcionan diferentes tipos de opciones de ExecutorService .
Dependiendo de sus requisitos, puede elegir uno de ellos o puede buscar ThreadPoolExecutor que proporciona un mejor control en el tamaño de cola de tareas delimitadas, mecanismos RejectedExecutionHandler
.
static ExecutorService newFixedThreadPool(int nThreads)
Crea un grupo de subprocesos que reutiliza una cantidad fija de subprocesos que operan desde una cola compartida ilimitada.
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Crea un grupo de subprocesos que puede programar comandos para que se ejecuten después de un retraso determinado o para ejecutar periódicamente.
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
Crea un grupo de subprocesos que crea subprocesos nuevos según sea necesario, pero reutilizará subprocesos construidos previamente cuando estén disponibles, y utiliza la herramienta ThreadFactory proporcionada para crear subprocesos nuevos cuando sea necesario.
static ExecutorService newWorkStealingPool(int parallelism)
Crea un grupo de subprocesos que mantiene suficientes subprocesos para admitir el nivel de paralelismo especificado y puede usar varias colas para reducir la contienda.
Cada una de estas API está dirigida a satisfacer las necesidades comerciales respectivas de su aplicación. Cuál usar dependerá de su requisito de uso.
p.ej
Si desea procesar todas las tareas enviadas por orden de llegada, solo use
newFixedThreadPool(1)
Si desea optimizar el rendimiento del gran cálculo de tareas recursivas, utilice
ForkJoinPool
onewWorkStealingPool
Si desea ejecutar algunas tareas periódicamente o en algún momento en el futuro, use
newScheduledThreadPool
Eche un vistazo a otro article agradable de PeterLawrey
sobre casos de uso de ExecutorService
.
Pregunta SE relacionada: