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


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 API respectiva, 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 de tenedor / unión parecen ser más rápidas (posiblemente debido al planificador 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 ForkJoinPool ha reemplazado a ThreadPoolExecutor.

¿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?


32
2018-02-14 12:23


origen


Respuestas:


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. P.ej. 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 el javadoc para ForkJoinPool:

esta clase proporciona métodos de comprobación de estado (por ejemplo, getStealCount ())   que están destinados a ayudar a desarrollar, ajustar y monitorear   aplicaciones fork / join.


14
2018-04-28 18:39



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 por   virtud de emplear el robo de trabajo: todos los hilos en el grupo intentan   encontrar y ejecutar tareas enviadas al grupo y / o creadas por otro   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   (al igual que la mayoría de ForkJoinTasks), así como cuando se realizan muchas tareas pequeñas   enviado al grupo de clientes externos. Especialmente cuando se configura   asyncMode en true en constructores, ForkJoinPools también puede ser   apropiado para usar con tareas de estilo de evento 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. Ver esta y esta.

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


10
2018-02-14 12:29



HASTA DONDE SE, ForkJoinPool funciona mejor si realiza un gran trabajo y desea 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 decir que cuando se trata de elementos de lógica empresarial relativamente aleatorios, ThreadPoolExecutor hará todo lo que necesita, así que, por qué hacerlo más complicado de lo que necesita.


2
2018-02-14 12:30



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 subprocesos inactivos.

Java 8 ha introducido una API más en Ejecutores - newWorkStealingPool para crear un pool de robo de trabajo No tienes 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 a diferencia de en 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:

Echa un vistazo a esto artículo para casos de uso de varios tipos de grupos de hilos del Servicio de Ejecutor y evaluación de ForkJoin Pool caracteristicas.


1
2018-01-18 13:19