Programação simultânea Java - qual pool de threads é usado na produção real (princípio ThreadPoolExecutor do pool de threads e perguntas da entrevista)

problema

Onde estão as vantagens e desvantagens dos pools de threads?
Qual é o princípio de implementação subjacente do pool de threads?
Como fazer bom uso do pool de threads no desenvolvimento diário?
Qual pool de threads é realmente usado?

Pool de threads ThreadPoolExecutor

Conceito
O trabalho realizado pelo pool de threads é principalmente para controlar o número de threads em execução. Durante o processamento, as tarefas são adicionadas à fila e, em seguida, essas tarefas são iniciadas após a criação do thread. Se o número máximo for excedido, o número excedente de threads aguardará na fila e por outros threads. Após a conclusão da execução, a tarefa é retirada da fila para execução.

Vantagens
Suas principais características são: reutilização de threads, controle do número máximo de simultaneidade e gerenciamento de threads.

  1. Reutilização de threads: não precisa manter novos threads, reutilize threads que foram criados para reduzir a sobrecarga de criação e destruição de threads e economize recursos do sistema.
  2. Melhorar a velocidade de resposta: Quando a tarefa for alcançada, não há necessidade de criar um novo encadeamento, use diretamente o encadeamento do pool de encadeamentos.
  3. Thread de gerenciamento: você pode controlar o número máximo de concorrentes, controlar a criação de threads, etc.

Sistema
ExecutorExecutorServiceAbstractExecutorServiceThreadPoolExecutor. ThreadPoolExecutorÉ a classe principal criada pelo pool de threads. Semelhante Arraysàs Collectionsferramentas, Executoreles também têm suas próprias ferramentas Executors.

Implementação de arquitetura

O pool de threads em Java é implementado por meio da estrutura Executor, que usa as classes Executor, Executors, ExecutorService e ThreadPoolExecutor.
Insira a descrição da imagem aqui

Como usar o pool de threads

Três maneiras comuns de criar pools de threads

newFixedThreadPool : Use a LinkedBlockingQueueimplementação, pool de threads de comprimento fixo.

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

newSingleThreadExecutor : Use a LinkedBlockingQueueimplementação, um pool tem apenas um thread.

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

newCachedThreadPool : Use a SynchronousQueueimplementação, pool de threads de comprimento variável.

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

Sete parâmetros do pool de threads

int corePoolSize,核心线程
int maximumPoolSize,非核心线程
long keepAliveTime,时间
TimeUnit unit,时间单位
BlockingQueue<Runnable> workQueue,队列
 ThreadFactory threadFactory,线程工厂
RejectedExecutionHandler handler 拒绝策略

Formato

parâmetro significado
corePoolSize Número de threads principais residentes no pool de threads
maximumPoolSize Número máximo de threads que podem ser acomodados
keepAliveTime Tempo de sobrevivência do segmento ocioso
unidade Unidade de tempo de sobrevivência
workQueue Uma fila que armazena tarefas enviadas, mas não executadas
threadFactory Aula de fábrica para a criação de threads
manipulador Política de rejeição depois que a fila de espera está cheia

Notas de método

corePoolSize - o número de threads a serem mantidos no pool, mesmo que estejam ociosos, a menos que allowCoreThreadTimeOut seja definido como
maximumPoolSize - o número máximo de threads a serem permitidos no pool
keepAliveTime - quando o número de threads for maior que o núcleo, este é o tempo máximo que os threads ociosos em excesso aguardarão por novas tarefas antes de encerrar.
unit - a unidade de tempo para o argumento keepAliveTime workQueue
- a fila a ser usada para manter as tarefas antes de serem executadas. Esta fila conterá apenas as tarefas executáveis ​​enviadas pelo método execute.
threadFactory - a fábrica a ser usada quando o executor cria um novo
manipulador de thread - o manipulador a ser usado quando a execução é bloqueada porque os limites do thread e as capacidades da fila são atingidos

Código

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    
    
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

Compreensão : Os parâmetros de criação do pool de threads são como um banco .

corePoolSizeAssim como a “ janela de plantão ” do banco , por exemplo, hoje existem 2 caixas atendendo solicitações (tarefas) de clientes . Se houver mais de 2 clientes, os novos clientes aguardarão na área de espera (fila de espera workQueue). Quando a área de espera estiver cheia, a " janela de horas extras " deve ser aberta neste momento para permitir que os outros 3 caixas façam horas extras, neste momento, a janela máximamaximumPoolSize é de 5. Se todas as janelas estiverem abertas e a área de espera ainda estiver cheia, a “ política de rejeiçãodeve ser acionada neste momento handler, informando ao afluxo de clientes para não entrar, pois ela está cheia. Uma vez que não há mais afluxo de novos clientes, o número de clientes que concluíram as obras aumentou, e a janela começou a ficar ociosa. Neste momento, ao keepAlivetTimecancelar as 3 "janelas de horas extras" extras, restaure para 2 "janelas de trabalho".

Processo

Se o número de threads em execução for menor que corePoolSize, crie um thread principal; se for maior ou igual a corePoolSize, coloque-o na fila de espera.

Se a fila de espera estiver cheia, mas o número de threads em execução for menor que o maximumPoolSize, crie um thread não principal; se for maior ou igual ao maximumPoolSize, inicie a estratégia de rejeição.

Quando um thread não tem nada a fazer por um período de keepAliveTime, se o número de threads em execução for maior que corePoolSize, o thread não principal é fechado.

Estratégia de rejeição de pool de threads

Insira a descrição da imagem aqui

Quando a fila de espera está cheia e o número máximo de threads é atingido e uma nova tarefa chega, é necessário iniciar a estratégia de rejeição. O JDK fornece quatro estratégias de rejeição, respectivamente.

  1. AbortPolicy : a política padrão, que lança RejectedExecutionExceptionuma exceção diretamente para evitar que o sistema funcione normalmente.
  2. CallerRunsPolicy : não lança uma exceção nem encerra a tarefa, mas retorna a tarefa ao chamador.
  3. DiscardOldestPolicy : descarta a tarefa de espera mais longa na fila e, em seguida, adicione a tarefa atual à fila e tente enviar a tarefa novamente.
  4. DiscardPolicy : descarta a tarefa diretamente, sem qualquer processamento.

Qual thread pool é usado na produção real (ênfase)?

Imagem da versão de Songshan do manual de desenvolvimento de Ali java
Insira a descrição da imagem aqui

Comprimento único, variável e fixo não são necessários ! A razão é, FixedThreadPoole SingleThreadExecutora camada inferior é usada LinkedBlockingQueue, o comprimento máximo da fila é alcançado Integer.MAX_VALUE, obviamente leva a OOM. Portanto, na produção real, você geralmente passa ThreadPoolExecutor7 parâmetros e personaliza o pool de threads.

ExecutorService threadPool=new ThreadPoolExecutor(2,5,
                        1L,TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(3),
                        Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.AbortPolicy());

Seleção de parâmetro de pool de thread personalizado

Tarefas com uso intensivo de CPU

Para tarefas com uso intensivo de CPU, o número máximo de threads é o número de threads de CPU + 1.

O uso intensivo da CPU significa que a tarefa requer muitos cálculos sem bloqueio. A CPU está funcionando em velocidade total.
As tarefas intensivas da CPU só podem ser aceleradas em uma verdadeira
CPU multi -core (por meio de multi-threading) e em uma CPU single-core (tragédia? ), não importa quantos multi-threads simulados você abra, a tarefa não pode ser acelerada, porque o poder de computação total da CPU é apenas isso.
Tarefas com uso intensivo de CPU configuram o menor número de threads possível:
fórmula geral: número de núcleos de CPU + 1 pool de threads

Tarefas intensivas de IO

Para tarefas intensivas de E / S, aloque tantos pontos quanto possível, que pode ser o número de threads de CPU * 2 ou o número de threads de CPU / (fator de bloqueio 1).

Experiência de uma grande fábrica

IO-intensivo, ou seja, a tarefa exige muito O, ou seja, muito bloqueio.
Executar tarefas intensivas
em IO em um único thread resultará no desperdício de muito poder de computação da CPU, desperdiçado na espera. Portanto, usar multithreading em tarefas intensivas em IO pode acelerar bastante a execução do programa. Mesmo em uma CPU de núcleo único, essa aceleração é principalmente Utilize o tempo de bloqueio desperdiçado.
Quando o IO é intensivo, a maioria dos threads é bloqueada, então você precisa configurar o número de threads.
Fórmula de referência: número do núcleo da CPU / coeficiente de bloqueio 1. O
coeficiente de bloqueio está entre 0,8 e 09. Por
exemplo, CPU de 8 núcleos: 8 /(1-0.9)=80 Tópicos

Fale sobre o princípio de funcionamento subjacente do pool de threads (ênfase)

Insira a descrição da imagem aqui

O seguinte conteúdo é muito importante, o seguinte conteúdo é muito importante, o seguinte conteúdo é muito importante

1. Após criar o pool de threads, aguarde a solicitação de tarefa enviada.
2. Quando o método execute (é chamado para adicionar uma tarefa de solicitação, o pool de threads fará os seguintes julgamentos
 2.1 Se o número de threads em execução for menor que corePoolSize, crie imediatamente uma thread para executar a tarefa;
 2.2 Se o número de threads em execução for maior ou igual a corePoolSize, então Coloque esta tarefa na fila
 2.3 Se a fila estiver cheia neste momento e o número de threads em execução for menor que maximumPoolSize, você ainda terá que criar threads não principais para executar esta tarefa imediatamente
 2.4 Se a fila estiver cheia e o número de threads em execução for maior ou igual a maximumPoolSize , Em seguida, o pool de threads iniciará a estratégia de rejeição de saturação para executar .
3. Quando um thread conclui a tarefa, ele vai tirar a próxima tarefa da fila para execução.
4. Quando um thread não tem nada para fazer por mais de um certo tempo (tempo de keepAlive) No momento, o pool de threads julgará:
se o número de threads atualmente em execução for maior que corePoolSize, então este thread será interrompido.
Portanto, depois que todas as tarefas do pool de threads forem concluídas, ele eventualmente encolherá para o tamanho de corePoolsize .

Como verificar o número de núcleos da CPU

Botão direito do mouse, não !!!

System.out.println(Runtime.getRuntime().availableProcessors());

Referência

Pool de encadeamentos de programação simultânea Java ThreadPoolExecutor usa
Ali boss para levá-lo a entender o princípio subjacente do conjunto de encadeamentos
JVM-JUC-Core
Ali manual de desenvolvimento de java Songshan version.pdf

Acho que você gosta

Origin blog.csdn.net/e891377/article/details/108737161
Recomendado
Clasificación