FixedThreadPool vs CachedThreadPool: el menor de dos males

93

Tengo un programa que genera subprocesos (~ 5-150) que realizan un montón de tareas. Originalmente, usé un FixedThreadPoolporque esta pregunta similar sugería que eran más adecuados para tareas de mayor duración y, con mi conocimiento muy limitado de subprocesos múltiples, consideré que la vida media de los subprocesos (varios minutos) era "de larga duración ".

Sin embargo, recientemente agregué la capacidad de generar subprocesos adicionales y hacerlo me lleva por encima del límite de subprocesos que establecí. En este caso, ¿sería mejor adivinar y aumentar el número de subprocesos que puedo permitir o cambiar a uno CachedThreadPoolpara no tener subprocesos desperdiciados?

Probándolos a ambos de manera preliminar, no parece haber una diferencia, así que me inclino a ir con los CachedThreadPooljustos para evitar el desperdicio. Sin embargo, ¿la vida útil de los subprocesos significa que debería elegir un FixedThreadPoolsubproceso y simplemente ocuparme de los subprocesos no utilizados? Esta pregunta hace que parezca que esos hilos adicionales no se desperdician, pero agradecería la aclaración.

Daniel
fuente

Respuestas:

108

Un CachedThreadPool es exactamente lo que debe usar para su situación, ya que no hay consecuencias negativas al usar uno para hilos de larga ejecución. El comentario en el documento de java acerca de que CachedThreadPools es adecuado para tareas cortas simplemente sugiere que son particularmente apropiados para tales casos, no que no puedan o no deban usarse para tareas que involucran tareas de larga ejecución.

Para desarrollar más, Executors.newCachedThreadPool y Executors.newFixedThreadPool están respaldados por la misma implementación del grupo de subprocesos (al menos en el JDK abierto) solo con diferentes parámetros. Las diferencias son el mínimo, el máximo, el tiempo de eliminación del subproceso y el tipo de cola.

public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
 }

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                 60L, TimeUnit.SECONDS,
                                 new SynchronousQueue<Runnable>());
}

Un FixedThreadPool tiene sus ventajas cuando, de hecho, desea trabajar con un número fijo de subprocesos, ya que entonces puede enviar cualquier número de tareas al servicio ejecutor sabiendo que el número de subprocesos se mantendrá en el nivel que especificó. Si desea explícitamente aumentar el número de subprocesos, esta no es la opción adecuada.

Sin embargo, esto significa que el único problema que puede tener con CachedThreadPool es limitar el número de subprocesos que se ejecutan al mismo tiempo. CachedThreadPool no los limitará por usted, por lo que es posible que deba escribir su propio código para asegurarse de no ejecutar demasiados subprocesos. Esto realmente depende del diseño de su aplicación y cómo se envían las tareas al servicio ejecutor.

Trevor Freeman
fuente
1
"Un CachedThreadPool es exactamente lo que debe usar para su situación, ya que no hay consecuencias negativas al usar uno para hilos de larga ejecución". No creo que esté de acuerdo. CachedThreadPool crea dinámicamente subprocesos sin límite superior. Las tareas de ejecución prolongada en una gran cantidad de subprocesos pueden acaparar potencialmente todos los recursos. Además, tener más subprocesos de lo ideal puede resultar en demasiados recursos desperdiciados en el cambio de contexto de estos subprocesos. Aunque explicó al final de la respuesta que se requiere una limitación personalizada, el comienzo de la respuesta es un poco engañoso.
Nishit
1
¿Por qué no crear simplemente un me ThreadPoolExecutorgusta acotado ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar
45

Ambos FixedThreadPooly CachedThreadPoolson males en aplicaciones muy cargadas.

CachedThreadPool es mas peligroso que FixedThreadPool

Si su aplicación está muy cargada y exige baja latencia, es mejor deshacerse de ambas opciones debido a los inconvenientes siguientes

  1. Naturaleza ilimitada de la cola de tareas: puede causar falta de memoria o alta latencia
  2. Los subprocesos de ejecución prolongada harán CachedThreadPoolque se salga de control en la creación del subproceso

Como sabes que ambos son males, el mal menor no hace el bien. Prefiera ThreadPoolExecutor , que proporciona un control granular sobre muchos parámetros.

  1. Configure la cola de tareas como cola limitada para tener un mejor control
  2. Tenga el RejectionHandler correcto: su propio RejectionHandler o los controladores predeterminados proporcionados por JDK
  3. Si tiene algo que hacer antes o después de completar la tarea, anule beforeExecute(Thread, Runnable)yafterExecute(Runnable, Throwable)
  4. Invalidar ThreadFactory si se requiere personalizar hilo
  5. Controle el tamaño del grupo de subprocesos dinámicamente en tiempo de ejecución (pregunta de SE relacionada: grupo de subprocesos dinámico )
Ravindra babu
fuente
¿Qué pasa si alguien decide utilizar commonPool?
Crosk Cool
1
@Ravindra: ha explicado maravillosamente las desventajas de CachedThreadPool y FixedThreadPool. Esto demuestra que tiene un conocimiento profundo del paquete de concurrencia.
Ayaskant
5

Entonces tengo un programa que genera subprocesos (~ 5-150) que realizan un montón de tareas.

¿Está seguro de que comprende cómo el sistema operativo y el hardware de su elección procesan los subprocesos? ¿Cómo asigna Java los subprocesos a los subprocesos del sistema operativo, cómo eso asigna los subprocesos a los subprocesos de la CPU, etc.? Lo pregunto porque crear 150 subprocesos dentro de ONE JRE solo tiene sentido si tiene núcleos / subprocesos de CPU masivos debajo, lo que probablemente no sea el caso. Dependiendo del sistema operativo y la RAM en uso, la creación de más de n subprocesos podría incluso provocar la terminación de su JRE debido a errores OOM. Por lo tanto, realmente debería distinguir entre los subprocesos y el trabajo a realizar por esos subprocesos, cuántos trabajos incluso puede procesar, etc.

Y ese es el problema con CachedThreadPool: no tiene sentido poner en cola el trabajo de larga ejecución en subprocesos que en realidad no se pueden ejecutar porque solo tiene 2 núcleos de CPU capaces de procesar esos subprocesos. Si termina con 150 subprocesos programados, puede crear una gran cantidad de gastos generales innecesarios para que los programadores utilizados en Java y el sistema operativo los procesen simultáneamente. Esto es simplemente imposible si solo tiene 2 núcleos de CPU, a menos que sus subprocesos estén esperando E / S o algo así todo el tiempo. Pero incluso en ese caso, muchos subprocesos crearían una gran cantidad de E / S ...

Y ese problema no ocurre con FixedThreadPool, creado, por ejemplo, con 2 + n subprocesos, donde n es razonablemente bajo, por supuesto, porque con ese hardware y los recursos del sistema operativo se utilizan con mucha menos sobrecarga para administrar subprocesos que no pueden ejecutarse de todos modos.

Thorsten Schöning
fuente
A veces no hay una mejor opción, solo podría tener 1 núcleo de CPU, pero si está ejecutando un servidor donde cada solicitud de usuario activaría un hilo para procesar la solicitud, no habrá ninguna otra opción razonable, especialmente si planea para escalar el servidor una vez que aumente su base de usuarios.
Michel Feinstein
@mFeinstein ¿Cómo puede uno no tener una opción si está en condiciones de elegir una implementación de grupo de subprocesos? En su ejemplo, con 1 núcleo de CPU que solo genera más subprocesos, simplemente no tiene ningún sentido, se ajusta perfectamente a mi ejemplo con FixedThreadPool. Eso también se escala fácilmente, primero con uno o dos hilos de trabajo, luego con 10 o 15, dependiendo de la cantidad de núcleos.
Thorsten Schöning
2
La gran mayoría de las implementaciones de servidores web crearán un nuevo hilo para cada nueva solicitud HTTP ... No les importará cuántos núcleos reales tenga la máquina, esto hace que la implementación sea más simple y más fácil de escalar. Esto se aplica a muchos otros diseños en los que solo desea codificar una vez e implementar, y no tener que volver a compilar y volver a implementar si cambia la máquina, que podría ser una instancia en la nube.
Michel Feinstein
@mFeinstein La mayoría de los servidores web usan grupos de subprocesos para solicitudes por sí mismos, simplemente porque generar subprocesos que no se pueden ejecutar no tiene sentido, o usan bucles de eventos para conexiones y procesan las solicitudes en grupos posteriormente o tal. Además, se está perdiendo el punto, que es que la pregunta es si uno puede elegir el grupo de subprocesos correcto y los subprocesos de generación que no pueden ejecutarse de todos modos todavía no tienen sentido. Un FixedthreadPool configurado para una cantidad razonable de hilos por máquina dependiendo de los núcleos escala bien.
Thorsten Schöning
3
@ ThorstenSchöning, tener 50 subprocesos vinculados a la CPU en una máquina de 2 núcleos no es útil. Tener 50 subprocesos enlazados a E / S en una máquina de 2 núcleos puede ser muy útil.
Paul Draper