De um modo geral, a velocidade das tarefas de produção é maior que a velocidade do consumo. Uma questão de detalhe é o comprimento da fila e como corresponder à velocidade de produção e consumo.
Um modelo típico de produtor-consumidor é o seguinte:
Para produção geral, é mais rápido que o consumo. Quando a fila está cheia, não queremos que nenhuma tarefa seja ignorada ou não executada. Continue enviando tarefas quando a fila não estiver cheia, então não há tempo ocioso desperdiçado. O bloqueio também é fácil.
Além disso, quando a fila está vazia, os consumidores não podem obter a tarefa, eles podem esperar um pouco antes de obtê -la. . Dessa forma, quando o produtor interromper a produção, os consumidores não esperam infinitamente.
Portanto, um modelo eficiente de produção de produção e consumo de suporte é implementado.
Espere, já que a JUC nos ajudou a implementar pools de threads, por que ainda precisamos usar esse conjunto de coisas? Não é mais conveniente usar o ExecorSerService diretamente?
Vamos dar uma olhada na estrutura básica do ThreadpoolExecutor:
Mas o problema é que, mesmo que você especifique manualmente um BlockingQueue como uma implementação da fila ao construir ThreadPoolExecutor, de fato, quando a fila estiver cheia, o método executivo não bloqueará.
A cópia do código é a seguinte:
public void execute (comando runnable) {
if (comando == null)
lançar novo nullPointerException ();
if (PoolSize> = CorePoolSize ||! AddifunderCorePoolSize (Command)) {
if (runState == Running && workqueue.offer (comando)) {
if (runState! = Running || poolsize == 0)
garantir a madrasta (comando);
}
caso contrário, if (! addifundermaximumpoolsize (comando))
rejeitar (comando);
}
}
Nesse momento, algo precisa ser feito para alcançar um resultado: quando o produtor envia uma tarefa e a fila está cheia, o produtor pode bloqueá -lo e aguardar a consumo da tarefa.
A chave é que, em um ambiente simultâneo, a fila completa não pode ser julgada pelo produtor e ThreadpoolExecutor.getQueue (). Size () não pode ser chamado para determinar se a fila está cheia.
Na implementação do pool de threads, quando a fila estiver cheia, o rejeito que o Handler passou durante a construção será chamado para rejeitar o processamento da tarefa. A implementação padrão é o abortpolicy, que lança diretamente uma rejeição rejeitada.
Não vou entrar em detalhes sobre várias estratégias de rejeição aqui. O consumo.
A cópia do código é a seguinte:
Classe estática pública CallErNSPolicy implementos rejeitEdExecutionHandler {
/**
* Cria um <tt> callErNSPolicy </tt>.
*/
public CallerUNSPolicy () {}
/**
* Executa a tarefa r no tópico do chamador, a menos que o executor
* foi desligado, caso em que a tarefa é descartada.
* @param r A tarefa executável solicitada a ser executada
* @param e o executor tentando executar esta tarefa
*/
public void rejeitEdExecution (Runnable R, ThreadpoolExecutor E) {
if (! E.ISSHUTDOWN ()) {
r.run ();
}
}
}
No entanto, essa estratégia também tem perigos ocultos. Continue produzindo as tarefas.
Referindo -se a idéias semelhantes, da maneira mais simples, podemos definir diretamente um rejeição rejeitado e alterá -lo para chamar BlockingQueue.put quando a fila está cheia para realizar o bloqueio do produtor:
A cópia do código é a seguinte:
new rejejeteExecutionHandler () {
@Override
public void rejeitEdExecution (Runnable R, ThreadpoolExecutor Executor) {
if (! Execoror.ISSHUTDOWN ()) {
tentar {
executor.getQueue (). put (r);
} catch (interruptedException e) {
// não deve ser interrompido
}
}
}
};
Dessa forma, não precisamos mais nos preocupar com a lógica da fila e do consumidor.
Comparado com o design original, esse método pode reduzir a quantidade de código e pode evitar muitos problemas em ambientes simultâneos. Obviamente, você também pode usar outros meios, como o uso de semáforos como limites de entrada ao enviar, mas se você quiser bloquear o produtor, ele parecerá complicado.