examples example ejemplo java parallel-processing threadpool threadpoolexecutor forkjoinpool

example - ¿Cuál es la ventaja de un Java-5 ThreadPoolExecutor sobre un Java-7 ForkJoinPool?



fork join java examples (4)

Java 5 ha introducido soporte para la ejecución asíncrona de tareas por un grupo de subprocesos en la forma del marco Ejecutor, cuyo corazón es el grupo de subprocesos implementado por java.util.concurrent.ThreadPoolExecutor. Java 7 ha agregado un grupo de subprocesos alternativo en la forma de java.util.concurrent.ForkJoinPool.

En cuanto a su respectiva API, ForkJoinPool proporciona un superconjunto de la funcionalidad de ThreadPoolExecutor en escenarios estándar (aunque estrictamente hablando, ThreadPoolExecutor ofrece más oportunidades de ajuste que ForkJoinPool). Agregando a esto la observación de que las tareas fork / join parecen ser más rápidas (posiblemente debido al programador de robo de trabajo), necesitan definitivamente menos subprocesos (debido a la operación de unión no bloqueante), uno puede tener la impresión de que ThreadPoolExecutor ha sido reemplazado por ForkJoinPool.

¿Pero esto es realmente correcto? Todo el material que he leído parece resumir una distinción bastante vaga entre los dos tipos de grupos de hilos:

  • ForkJoinPool es para muchas tareas dependientes, generadas por tareas, breves, casi nunca bloqueantes (es decir, intensivas en cómputo)
  • ThreadPoolExecutor es para pocas tareas independientes, generadas externamente, largas, a veces bloqueantes

¿Es esta distinción correcta en absoluto? ¿Podemos decir algo más específico sobre esto?


AFAIK, ForkJoinPool funciona mejor si tienes un trabajo grande y quieres que se descomponga automáticamente. ThreadPoolExecutor es una mejor opción si sabes cómo quieres que se rompa el trabajo. Por esta razón, tiendo a usar este último porque he determinado cómo quiero que se rompa el trabajo. Como tal, no es para todos.

No vale la pena que cuando se trata de elementos de lógica de negocios relativamente aleatorios, un ThreadPoolExecutor hará todo lo que necesita, entonces, ¿por qué hacerlo más complicado de lo que necesita?


Comparemos las diferencias en constructores:

ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

ForkJoinPool

ForkJoinPool(int parallelism, ForkJoinPool.ForkJoinWorkerThreadFactory factory, Thread.UncaughtExceptionHandler handler, boolean asyncMode)

La única ventaja que he visto en ForkJoinPool : Mecanismo de robo de trabajo por hilos inactivos.

Java 8 ha introducido una API más en los ejecutores - newWorkStealingPool para crear pool de robo de trabajo. No tiene que crear RecursiveTask y RecursiveAction pero aún puede usar ForkJoinPool .

public static ExecutorService newWorkStealingPool()

Crea un grupo de subprocesos de robo de trabajo utilizando todos los procesadores disponibles como su nivel de paralelismo de destino.

Ventajas de ThreadPoolExecutor sobre ForkJoinPool:

  1. Puede controlar el tamaño de la cola de tareas en ThreadPoolExecutor diferencia de ForkJoinPool .
  2. Puede aplicar la política de rechazo cuando se agote su capacidad a diferencia de ForkJoinPool

Me gustan estas dos características en ThreadPoolExecutor que mantiene la salud del sistema en buen estado.

EDITAR:

Eche un vistazo a este artículo para casos de uso de varios tipos de grupos de hilos del Servicio Executor y evaluación de las características del Pool ForkJoin .


Lectura recomendada http://gee.cs.oswego.edu/dl/jsr166/dist/docs/ De los documentos para ForkJoinPool:

Un ForkJoinPool difiere de otros tipos de ExecutorService principalmente en virtud del empleo de robo de trabajo: todos los subprocesos del grupo intentan encontrar y ejecutar tareas enviadas al grupo y / o creadas por otras tareas activas (eventualmente bloqueando la espera de trabajo si no existe) . Esto permite un procesamiento eficiente cuando la mayoría de las tareas generan otras subtareas (como la mayoría de ForkJoinTasks), así como cuando se envían muchas tareas pequeñas al grupo desde clientes externos. Especialmente cuando se establece asyncMode en true en los constructores, ForkJoinPools también puede ser apropiado para usar con tareas de eventos que nunca se unen.

El marco de unión de horquilla es útil para la ejecución en paralelo, mientras que el servicio de ejecutor permite la ejecución simultánea y existe una diferencia. Mira esto y esto .

El marco de unión de la horquilla también permite el robo de trabajo (uso de una Deque).

Este artículo es una buena lectura.


ThreadPool (TP) y ForkJoinPool (FJ) están dirigidos a diferentes casos de uso. La principal diferencia está en el número de colas empleadas por los diferentes ejecutores que deciden qué tipo de problemas son más adecuados para cualquiera de los ejecutores.

El ejecutor FJ tiene n (alias nivel de paralelismo) colas simultáneas separadas (deques) mientras que el ejecutor TP tiene solo una cola concurrente (estas colas / deques pueden ser implementaciones personalizadas que no siguen la API de Colecciones JDK). Como resultado, en escenarios en los que tiene una gran cantidad de tareas (generalmente de ejecución relativamente corta) generadas, el ejecutor FJ tendrá un mejor rendimiento ya que las colas independientes minimizarán las operaciones simultáneas y los robos infrecuentes ayudarán con el equilibrio de carga. En TP debido a la cola única, habrá operaciones simultáneas cada vez que se deque el trabajo y actuará como un cuello de botella relativo y limitará el rendimiento.

Por el contrario, si hay relativamente pocas tareas de larga ejecución, la cola única en TP ya no es un cuello de botella para el rendimiento. Sin embargo, las colas n-independientes y los intentos de robo de trabajo relativamente frecuentes se convertirán en un cuello de botella en FJ ya que posiblemente haya muchos intentos inútiles de robar trabajo que se agreguen a gastos generales.

Además, el algoritmo de robo de trabajo en FJ supone que tareas (más antiguas) robadas de la deque producirán suficientes tareas paralelas para reducir el número de robos. Por ejemplo, en quicksort o mergesort, donde las tareas más antiguas equivalen a matrices más grandes, estas tareas generarán más tareas y mantendrán la cola no vacía y reducirán el número total de robos. Si este no es el caso en una aplicación determinada, los intentos de robo frecuentes vuelven a convertirse en un cuello de botella. Esto también se observa en javadoc para ForkJoinPool :

esta clase proporciona métodos de comprobación de estado (por ejemplo, getStealCount ()) que están destinados a ayudar en el desarrollo, ajuste y monitoreo de aplicaciones fork / join.