En términos generales, la velocidad de las tareas de producción es mayor que la velocidad del consumo. Una cuestión de detalle es la longitud de la cola y cómo igualar la velocidad de producción y consumo.
Un modelo típico de consumidor de productores es el siguiente:
Para la producción general es más rápido que el consumo. Cuando la cola está llena, no queremos que se ignore o no se ejecute en este momento, el productor puede esperar un momento antes de enviar la tarea. . El bloqueo también es fácil.
Además, cuando la cola está vacía, los consumidores no pueden obtener la tarea, pueden esperar un tiempo antes de obtenerla. . Se recomienda llamar a Take. De esta manera, cuando el productor ha detenido la producción, los consumidores no esperarán infinitamente.
Por lo tanto, se implementa un modelo de producción eficiente que respalda el bloqueo.
Espera, ya que JUC nos ha ayudado a implementar grupos de hilos, ¿por qué todavía necesitamos usar este conjunto de cosas? ¿No es más conveniente usar ExecutorService directamente?
Echemos un vistazo a la estructura básica de ThreadPoolExecutor:
Pero el problema es que incluso si especifica manualmente una Bloquea como una implementación de cola al construir ThreadPoolExecutor, de hecho, cuando la cola está llena, el método de ejecución no bloqueará la razón es que ThreadPoolExecutor llama al método de oferta sin bloqueo de bloqueo:
La copia del código es la siguiente:
public void ejecute (comando runnable) {
if (command == null)
tirar nueva nullpointerException ();
if (PoolSize> = CorePoolSize ||
if (runstate == running && workqueue.offer (comando)) {
if (runState! = Running || PoolSize == 0)
ASARCELEEDTASKHANDLED (comando);
}
else if (! addifUnMeMaxImumpeolSize (comando))
rechazar (comando);
}
}
En este momento, se debe hacer algo para lograr un resultado: cuando el productor envía una tarea y la cola está llena, el productor puede bloquearlo y esperar a que se consuma la tarea.
La clave es que en un entorno concurrente, el productor no puede juzgar la cola llena, y ThreadpoolExecutor.getqueue (). Size () no se puede llamar para determinar si la cola está llena.
En la implementación del grupo de subprocesos, cuando la cola está llena, el DelechEdedExecutionHandler aprobado durante la construcción será llamado a rechazar el procesamiento de la tarea. La implementación predeterminada es abortpolicy, que lanza directamente una concepción de Execución rechazada.
No entraré en detalles sobre varias estrategias de rechazo aquí. El consumo.
La copia del código es la siguiente:
clase pública estática Callerrunspolicy implementa rechazarexecutionHandler {
/**
* Crea un <tt> CallerrunSpolicy </tt>.
*/
público CallerrunSpolicy () {}
/**
* Ejecuta la tarea R en el hilo de la persona que llama, a menos que el ejecutor
* se ha cerrado, en cuyo caso se descarta la tarea.
* @param r La tarea ejecutable solicitada para ser ejecutada
* @param e el ejecutor que intenta ejecutar esta tarea
*/
public void rechazadoxecution (runnable r, threadpoolexecutor e) {
if (! e.isshutdown ()) {
r.run ();
}
}
}
Sin embargo, esta estrategia también tiene peligros ocultos. Continuar produciendo las tareas.
Refiriéndose a ideas similares, la forma más simple, podemos definir directamente un DecheedExecutionHandler y cambiarlo para llamar a Bloquingqueue.
La copia del código es la siguiente:
New RecheDedExecutionHandler () {
@Anular
public void rechazadoxecution (runnable r, threadpoolexecutor ejecutor) {
if (! Ejecutor.isshutdown ()) {
intentar {
ejecutor.getqueue (). Put (r);
} capt (interruptedException e) {
// no debe ser interrumpido
}
}
}
};
De esta manera, ya no necesitamos preocuparnos por la lógica de la cola y el consumidor.
En comparación con el diseño original, este método puede reducir la cantidad de código y puede evitar muchos problemas en entornos concurrentes. Por supuesto, también puede usar otros medios, como usar semáforos como límites de entrada al enviar, pero si solo desea bloquear al productor, parecerá complicado.