newCachedThreadPool()
versus newFixedThreadPool()
¿Cuándo debo usar uno u otro? ¿Qué estrategia es mejor en términos de utilización de recursos?
newCachedThreadPool()
versus newFixedThreadPool()
¿Cuándo debo usar uno u otro? ¿Qué estrategia es mejor en términos de utilización de recursos?
Creo que los documentos explican la diferencia y el uso de estas dos funciones bastante bien:
Crea un grupo de subprocesos que reutiliza un número fijo de subprocesos que operan en una cola compartida no acotada. En cualquier momento, a lo sumo, los hilos nThreads 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 algún subproceso termina debido a un error durante la ejecución antes del apagado, uno nuevo tomará su lugar si es necesario para ejecutar tareas posteriores. Los subprocesos en el grupo existirán hasta que se cierre explícitamente.
Crea un grupo de subprocesos que crea nuevos subprocesos según sea necesario, pero reutilizará los subprocesos construidos previamente cuando estén disponibles. Estos grupos generalmente mejorarán el rendimiento de los programas que ejecutan muchas tareas asincrónicas de corta duración. Las llamadas a ejecutar reutilizarán hilos previamente construidos si están disponibles. Si no hay un hilo existente disponible, se creará un nuevo hilo y se agregará al grupo. Los subprocesos que no se han utilizado durante sesenta segundos se terminan y se eliminan de la memoria caché. Por lo tanto, un grupo que permanezca inactivo durante el tiempo suficiente no consumirá ningún recurso. Tenga en cuenta que los grupos con propiedades similares pero detalles diferentes (por ejemplo, parámetros de tiempo de espera) se pueden crear utilizando los constructores ThreadPoolExecutor.
En términos de recursos, newFixedThreadPool
mantendrá todos los subprocesos en ejecución hasta que finalicen explícitamente. En elnewCachedThreadPool
subprocesos que no se han utilizado durante sesenta segundos se terminan y se eliminan de la memoria caché.
Ante esto, el consumo de recursos dependerá mucho de la situación. Por ejemplo, si tiene una gran cantidad de tareas de larga ejecución, sugeriría el FixedThreadPool
. En cuanto a la CachedThreadPool
, los documentos dicen que "estos grupos generalmente mejorarán el rendimiento de los programas que ejecutan muchas tareas asincrónicas de corta duración".
newCachedThreadPool
podría causar algunos problemas serios porque deja todo el control althread pool
y cuando el servicio está trabajando con otros en el mismo host , lo que puede causar que los demás se bloqueen debido a la larga espera de la CPU. Así que creo quenewFixedThreadPool
puede ser más seguro en este tipo de escenario. También este poste aclara las diferencias más importantes entre ellos.Solo para completar las otras respuestas, me gustaría citar Effective Java, 2nd Edition, de Joshua Bloch, capítulo 10, Artículo 68:
fuente
Si observa el código fuente , verá que están llamando a ThreadPoolExecutor. internamente y estableciendo sus propiedades. Puede crear uno para tener un mejor control de sus requisitos.
fuente
Si no le preocupa una cola ilimitada de tareas ejecutables / ejecutables , puede usar una de ellas. Como sugirió bruno, yo también prefiero
newFixedThreadPool
anewCachedThreadPool
estos dos.Pero ThreadPoolExecutor proporciona características más flexibles en comparación con cualquiera
newFixedThreadPool
onewCachedThreadPool
Ventajas:
Tienes control total del tamaño de BlockingQueue . No es ilimitado, a diferencia de las dos opciones anteriores. No obtendré un error de falta de memoria debido a una gran acumulación de tareas pendientes ejecutables / ejecutables cuando haya una turbulencia inesperada en el sistema.
Puede implementar una política de manejo de rechazo personalizada O utilizar una de las políticas:
En el valor predeterminado
ThreadPoolExecutor.AbortPolicy
, el controlador arroja un tiempo de ejecución RejectedExecutionException tras el rechazo.En
ThreadPoolExecutor.CallerRunsPolicy
, el hilo que invoca ejecutarse ejecuta la tarea. Esto proporciona un mecanismo de control de retroalimentación simple que reducirá la velocidad de envío de nuevas tareas.En
ThreadPoolExecutor.DiscardPolicy
, una tarea que no se puede ejecutar simplemente se descarta.En
ThreadPoolExecutor.DiscardOldestPolicy
, si el ejecutor no se cierra, la tarea en la cabecera de la cola de trabajo se descarta y luego se vuelve a intentar la ejecución (que puede fallar nuevamente y hacer que esto se repita).Puede implementar una fábrica de subprocesos personalizada para los siguientes casos de uso:
fuente
Así es,
Executors.newCachedThreadPool()
no es una gran opción para el código de servidor que atiende a múltiples clientes y solicitudes concurrentes.¿Por qué? Básicamente hay dos problemas (relacionados) con él:
No tiene límites, lo que significa que está abriendo la puerta para que cualquier persona paralice su JVM simplemente inyectando más trabajo en el servicio (ataque DoS). Los subprocesos consumen una cantidad de memoria no despreciable y también aumentan el consumo de memoria en función de su trabajo en progreso, por lo que es bastante fácil derribar un servidor de esta manera (a menos que tenga otros interruptores en su lugar).
El problema ilimitado se ve exacerbado por el hecho de que el Ejecutor está al frente de un
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. Esta es generalmente 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 subprocesos definitivamente no es lo que necesita el servidor.Aquí están mis recomendaciones:
Utilice un grupo de subprocesos de tamaño fijo Executors.newFixedThreadPool o un ThreadPoolExecutor. con un número máximo de hilos establecido;
fuente
La
ThreadPoolExecutor
clase es la implementación base para los ejecutores que se devuelven de muchos de losExecutors
métodos de fábrica. Entonces acerquémonos a los grupos de subprocesos fijos y en caché deThreadPoolExecutor
la perspectiva de.ThreadPoolExecutor
El constructor principal de esta clase se ve así:
Tamaño de la piscina central
El
corePoolSize
determina el tamaño mínimo de la agrupación de hebras diana.La implementación mantendría un grupo de ese tamaño incluso si no hay tareas para ejecutar.Tamaño máximo de piscina
El
maximumPoolSize
es el número máximo de subprocesos que pueden estar activos a la vez.Después de que el grupo de subprocesos crezca y se vuelva más grande que el
corePoolSize
umbral, el ejecutor puede terminar los subprocesos inactivos y alcanzar elcorePoolSize
nuevo. SiallowCoreThreadTimeOut
es cierto, entonces el ejecutor puede incluso terminar los subprocesos de la agrupación central si estuvieran inactivos por encima delkeepAliveTime
umbral.Por lo tanto, la conclusión es que si los subprocesos permanecen inactivos más allá del
keepAliveTime
umbral, pueden terminarse ya que no hay demanda para ellos.Haciendo cola
¿Qué sucede cuando entra una nueva tarea y todos los hilos centrales están ocupados? Las nuevas tareas se pondrán en cola dentro de esa
BlockingQueue<Runnable>
instancia. Cuando un hilo se libera, se puede procesar una de esas tareas en cola.Existen diferentes implementaciones de la
BlockingQueue
interfaz en Java, por lo que podemos implementar diferentes enfoques de colas como:Cola limitada : las nuevas tareas se pondrían en cola dentro de una cola de tareas limitada.
Cola sin límites : las nuevas tareas se pondrían en cola dentro de una cola de tareas sin límites. Por lo tanto, esta cola puede crecer tanto como lo permita el tamaño del almacenamiento dinámico.
Traspaso sincrónico : también podemos usar el
SynchronousQueue
para poner en cola las nuevas tareas. En ese caso, al poner en cola una nueva tarea, otro hilo ya debe estar esperando esa tarea.Sumisión de trabajo
Así es como
ThreadPoolExecutor
ejecuta una nueva tarea:corePoolSize
están ejecutando subprocesos, intenta iniciar un nuevo subproceso con la tarea dada como primer trabajo.BlockingQueue#offer
método Eloffer
método no se bloqueará si la cola está llena e inmediatamente regresafalse
.offer
retornafalse
), intenta agregar un nuevo subproceso al grupo de subprocesos con esta tarea como su primer trabajo.RejectedExecutionHandler
.La principal diferencia entre los grupos de subprocesos fijos y en caché se reduce a estos tres factores:
Grupo de subprocesos fijos
Así es como
Excutors.newFixedThreadPool(n)
funciona:Como puedes ver:
OutOfMemoryError
.Un grupo de subprocesos de tamaño fijo parece ser un buen candidato cuando vamos a limitar el número de tareas concurrentes para fines de gestión de recursos .
Por ejemplo, si vamos a usar un ejecutor para manejar las solicitudes del servidor web, un ejecutor fijo puede manejar las ráfagas de solicitudes de manera más razonable.
Para una mejor gestión de los recursos, es muy recomendable crear una aplicación personalizada
ThreadPoolExecutor
con unaBlockingQueue<T>
implementación limitada y razonableRejectedExecutionHandler
.Grupo de subprocesos en caché
Así es como
Executors.newCachedThreadPool()
funciona:Como puedes ver:
Integer.MAX_VALUE
. Prácticamente, el grupo de subprocesos no tiene límites.SynchronousQueue
siempre falla cuando no hay nadie en el otro extremo que lo acepte.Úselo cuando tenga muchas tareas predecibles de corta duración.
fuente
Debe usar newCachedThreadPool solo cuando tenga tareas asincrónicas de corta duración como se indica en Javadoc, si envía tareas que requieren más tiempo para procesar, terminará creando demasiados hilos. Puede alcanzar el 100% de la CPU si envía tareas de ejecución prolongada a un ritmo más rápido a newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ).
fuente
Hago algunas pruebas rápidas y tengo los siguientes hallazgos:
1) si usa SynchronousQueue:
Después de que los subprocesos alcancen el tamaño máximo, cualquier trabajo nuevo será rechazado con la excepción que se muestra a continuación.
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 el tamaño mínimo.
fuente