Executors.newCachedThreadPool () versus Executors.newFixedThreadPool ()

newCachedThreadPool() versus newFixedThreadPool()

¿Cuándo debería usar uno u otro? ¿Qué estrategia es mejor en términos de utilización de recursos?

Creo que los documentos explican bastante bien la diferencia y el uso de estas dos funciones:

newFixedThreadPool

Crea un grupo de subprocesos que reutiliza una cantidad fija de subprocesos que operan desde una cola compartida ilimitada. En cualquier punto, como mucho nThreads los hilos serán tareas de procesamiento activas. Si se envían tareas adicionales cuando todos los hilos están activos, esperarán en la cola hasta que haya un hilo disponible. Si un hilo termina debido a un fallo durante la ejecución antes del apagado, uno nuevo tomará su lugar si es necesario para ejecutar tareas posteriores. Los hilos en el grupo existirán hasta que se cierre explícitamente.

newCachedThreadPool

Crea un grupo de subprocesos que crea subprocesos nuevos según sea necesario, pero reutilizará subprocesos construidos previamente cuando estén disponibles. Estas agrupaciones generalmente mejorarán el rendimiento de los progtwigs que ejecutan muchas tareas asincrónicas de corta duración. Las llamadas a ejecutar reutilizarán subprocesos construidos previamente si están disponibles. Si no hay ningún hilo existente disponible, se creará un nuevo hilo y se agregará al grupo. Los hilos que no se han usado durante sesenta segundos se terminan y se eliminan de la memoria caché. Por lo tanto, un grupo que permanece inactivo durante el tiempo suficiente no consumirá ningún recurso. Tenga en cuenta que se pueden crear grupos con propiedades similares pero diferentes detalles (por ejemplo, parámetros de tiempo de espera) utilizando constructores ThreadPoolExecutor.

En términos de recursos, newFixedThreadPool mantendrá todos los subprocesos en ejecución hasta que finalicen explícitamente. En newCachedThreadPool Threads que no se han utilizado durante sesenta segundos se cancelan y eliminan de la memoria caché.

Dado esto, el consumo de recursos dependerá mucho de la situación. Por ejemplo, si tiene una gran cantidad de tareas de ejecución prolongada, sugeriría FixedThreadPool . En cuanto a CachedThreadPool , los documentos dicen que “estos grupos generalmente mejorarán el rendimiento de los progtwigs que ejecutan muchas tareas asincrónicas de corta duración”.

Solo para completar las otras respuestas, me gustaría citar Effective Java, 2nd Edition, por Joshua Bloch, capítulo 10, artículo 68:

“Elegir el servicio del ejecutor para una aplicación en particular puede ser complicado. Si está escribiendo un progtwig pequeño o un servidor con poca carga , usar Executors.new-CachedThreadPool es generalmente una buena opción , ya que no requiere configuración y generalmente” hace el Lo correcto “. ¡Pero un grupo de subprocesos en caché no es una buena opción para un servidor de producción muy cargado !

En un grupo de subprocesos en caché , las tareas enviadas no se ponen en cola, sino que se transfieren inmediatamente a un subproceso para su ejecución. Si no hay subprocesos disponibles, se crea uno nuevo . Si un servidor está tan cargado que todas sus CPU se utilizan por completo y llegan más tareas, se crearán más subprocesos, lo que empeorará las cosas.

Por lo tanto, en un servidor de producción muy cargado , es mucho mejor utilizar Executors.newFixedThreadPool , que le proporciona un grupo con un número fijo de subprocesos o utiliza directamente la clase ThreadPoolExecutor para obtener el máximo control.

Si ves el código en grepcode, verás, están llamando a ThreadPoolExecutor. internamente y establecer sus propiedades. Puede crear uno para tener un mejor control de sus requisitos.

 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } 

Correcto, Executors.newCachedThreadPool() no es una gran opción para el código del servidor que atiende a varios clientes y solicitudes simultáneas.

¿Por qué? Básicamente, hay dos problemas (relacionados) con él:

  1. No tiene límites, lo que significa que está abriendo la puerta para que cualquiera pueda paralizar su JVM simplemente inyectando más trabajo en el servicio (ataque DoS). Los subprocesos consumen una cantidad no despreciable de memoria y también aumentan el consumo de memoria en función de su trabajo en curso, por lo que es bastante fácil volcar un servidor de esta manera (a menos que tenga otros interruptores en su lugar).

  2. El problema ilimitado se ve agravado por el hecho de que Executor está liderado por una SynchronousQueue que significa que hay una transferencia directa entre el encargado de la tarea y el grupo de subprocesos. Cada nueva tarea creará un nuevo hilo si todos los hilos existentes están ocupados. En general, esta es una mala estrategia para el código del servidor. Cuando la CPU se satura, las tareas existentes tardan más en finalizar. Sin embargo, se envían más tareas y se crean más hilos, por lo que las tareas tardan más y más en completarse. Cuando la CPU está saturada, más hilos definitivamente no es lo que necesita el servidor.

Aquí están mis recomendaciones:

Use un grupo de subprocesos de tamaño fijo Executors.newFixedThreadPool o ThreadPoolExecutor. con un número máximo establecido de hilos;

Si no está preocupado por la cola ilimitada de tareas invocables / ejecutables , puede usar una de ellas. Como lo sugirió bruno, yo también prefiero newFixedThreadPool a newCachedThreadPool entre estos dos.

Pero ThreadPoolExecutor ofrece funciones más flexibles en comparación con newFixedThreadPool o newCachedThreadPool

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

Ventajas:

  1. Usted tiene control total sobre el tamaño de BlockingQueue . No es ilimitado a diferencia de las dos opciones anteriores. No saldré del error de memoria debido al gran astackmiento de tareas Pendientes / Invocables pendientes en una turbulencia inesperada en el sistema.

  2. Puede implementar una política de manejo de rechazo personalizada O usar una de las siguientes políticas:

    1. En el valor predeterminado ThreadPoolExecutor.AbortPolicy , el controlador arroja un tiempo de ejecución RejectedExecutionException en el momento del rechazo.

    2. En ThreadPoolExecutor.CallerRunsPolicy , el hilo que invoca ejecutar se ejecuta la tarea. Esto proporciona un mecanismo de control de retroalimentación simple que ralentizará la velocidad con la que se envían las tareas nuevas.

    3. En ThreadPoolExecutor.DiscardPolicy , una tarea que no se puede ejecutar simplemente se descarta.

    4. En ThreadPoolExecutor.DiscardOldestPolicy , si el ejecutor no se cierra, la tarea en la cabecera de la cola de trabajo se elimina y luego se vuelve a intentar la ejecución (que puede fallar nuevamente, haciendo que esto se repita).

  3. Puede implementar Factory Thread personalizado para los casos de uso a continuación:

    1. Para establecer un nombre de hilo más descriptivo
    2. Para configurar el estado del daemon de subproceso
    3. Para establecer la prioridad del hilo

Debe usar newCachedThreadPool solo cuando tenga tareas asincrónicas de corta duración como se indica en Javadoc, si envía tareas que tardan más en procesarse, terminará creando demasiados hilos. Puede aplicar 100% de CPU si envía tareas de ejecución larga a mayor velocidad a newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ).

Hago algunas pruebas rápidas y tengo los siguientes hallazgos:

1) si usa SynchronousQueue:

Una vez que los hilos alcanzan el tamaño máximo, cualquier trabajo nuevo será rechazado con la excepción que se muestra a continuación.

Excepción en el hilo “principal” java.util.concurrent.RejectedExecutionException: Tarea java.util.concurrent.FutureTask@3fee733d rechazada de java.util.concurrent.ThreadPoolExecutor@5acf9800 [En ejecución, tamaño del grupo = 3, hilos activos = 3, tareas en cola = 0, tareas completadas = 0]

en java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)

2) si usa LinkedBlockingQueue:

Los subprocesos nunca aumentan del tamaño mínimo al tamaño máximo, lo que significa que el grupo de subprocesos tiene un tamaño fijo como tamaño mínimo.