【2023】 Resumo da explicação do pool de threads multi-threading Java (incluindo exemplos de código)

1. Três razões principais pelas quais JAVA usa pools de threads

  1. Criar/destruir threads consome recursos do sistema e o pool de threads pode reutilizar os threads criados .
  2. Controle a quantidade de simultaneidade . Muitas simultaneidades podem causar consumo excessivo de recursos e causar falha no servidor. (razão principal)
  3. Pode realizar gerenciamento unificado de linhas.

Há um total de 7 maneiras de criar um pool de threads, que geralmente são divididos em 2 categorias:

1. Pool de threads criado por ThreadPoolExecutor;

2. Pool de threads criado por Executores.

2. Como criar um conjunto de threads:

  • Executors.newFixedThreadPool: Crie um pool de threads de tamanho fixo para controlar o número de threads simultâneos. Threads excedentes aguardarão na fila;
  • Executors.newCachedThreadPool: Crie um pool de threads armazenável em cache. Se o número de threads exceder os requisitos de processamento, o cache será reciclado após um período de tempo. Se o número de threads não for suficiente, novos threads serão criados;
  • Executors.newSingleThreadExecutor: Crie um pool de threads com um único número de threads, que pode garantir a ordem de execução primeiro a entrar, primeiro a sair;
  • Executors.newScheduledThreadPool: Crie um pool de threads que pode executar tarefas atrasadas;
  • Executors.newSingleThreadScheduledExecutor: Crie um pool de threads de thread único que pode executar tarefas atrasadas;
  • Executors.newWorkStealingPool: Crie um pool de threads para execução preemptiva (a ordem de execução da tarefa é incerta) [Adicionado no JDK1.8].
  • ThreadPoolExecutor: A forma mais original de criar um pool de threads. Contém 7 parâmetros para definir, que serão discutidos em detalhes posteriormente.

Como adicionar tarefas:
*Use submit para executar tarefas com ou sem valores de retorno; enquanto execute só pode executar tarefas sem valores de retorno. *****

Crie um thread usando uma fábrica de threads:

Insira a descrição da imagem aqui

Recursos fornecidos:

1. Defina as regras de nomenclatura para threads (no pool de threads).

2. Defina a prioridade do thread.

3. Configure o agrupamento de threads.

4. Defina o tipo de thread (thread do usuário, thread daemon).

3. Crie um conjunto de threads

1. Crie um pool de threads de tamanho fixo
Insira a descrição da imagem aqui

2. Crie um pool de threads armazenável em cache e sinta o número de tarefas de thread criadas

  1. Vantagens: Esses threads podem ser reutilizados dentro de um determinado período de tempo para gerar um pool de threads correspondente.
  2. Desvantagens: É adequado para cenários com grande número de tarefas em um curto período de tempo, mas a desvantagem é que pode ocupar muitos recursos.
    Insira a descrição da imagem aqui

3. Crie um pool de threads de tarefas agendadas atrasadas.
3. ScheduleAtFixedRate é o horário de início da tarefa anterior, que é usado como horário de referência para a próxima tarefa agendada (tempo de referência + tarefa atrasada = execução da tarefa). *
4. scheduleWithFixedDelay é o horário de término da tarefa anterior, que é usado como horário de referência para a próxima tarefa agendada. *
5. Schedule (): Você pode definir o tempo de atraso e executá-lo regularmente;
Insira a descrição da imagem aqui
4. Crie um pool de threads de thread único
Insira a descrição da imagem aqui
5. Crie um pool de threads preemptivo
Insira a descrição da imagem aqui

⭐6. Use ThreadPoolExecutor para criar
Descrição do parâmetro ThreadPoolExecutor:
Insira a descrição da imagem aqui
Política de negação

  • JDK 4 tipos + 1
    Insira a descrição da imagem aqui
    código específico de estratégia personalizada
    Insira a descrição da imagem aqui

Processo de implementação

  1. O número total de threads < corePoolSize, não importa se o thread está ocioso ou não, um novo thread principal será criado para executar a tarefa (deixe o número de threads principais atingir rapidamente corePoolSize, quando o número de threads principais < corePoolSize )
    . Observe que esta etapa requer a obtenção de um bloqueio global.
  2. Quando o número total de threads >= corePoolSize, novas tarefas de thread entrarão na fila de tarefas para esperar e, em seguida, os threads principais ociosos buscarão sequencialmente tarefas da fila de cache para execução (refletindo a reutilização de threads ) .
  3. Quando a fila do cache está cheia, significa que há muitas tarefas no momento e alguns "trabalhadores temporários" são necessários para executar essas tarefas. Portanto, um thread não principal será criado para realizar esta tarefa. Observe que esta etapa requer a obtenção de um bloqueio global.
  4. Se a fila de cache estiver cheia e o número total de threads atingir o máximoPoolSize, será adotada a política de rejeição mencionada acima.
    Insira a descrição da imagem aqui

fila de bloqueio

Assumimos um cenário em que os produtores continuam produzindo recursos e os consumidores continuam consumindo recursos. Os recursos são armazenados em um buffer pool. O produtor armazena os recursos produzidos no buffer pool. O consumidor obtém os recursos do buffer pool para consumo. Este é o cenário famoso modelo produtor-consumidor .

Esse padrão pode simplificar o processo de desenvolvimento. Por um lado, elimina a dependência de código entre a classe produtora e a classe consumidora. Por outro lado, desacopla o processo de produção de dados do processo de utilização de dados para simplificar a carga.

Quando codificamos para implementar esse padrão, porque vários threads precisam operar variáveis ​​​​compartilhadas (ou seja, recursos), é fácil causar problemas de segurança de thread , causando consumo repetido e impasses , especialmente quando há vários produtores e consumidores. Além disso, quando o buffer pool está vazio, precisamos bloquear o consumidor e acordar o produtor; quando o buffer pool está cheio, precisamos bloquear o produtor e acordar o consumidor. Essas lógicas de espera-despertar precisam ser implementadas por nós mesmos.

BlockingQueue é uma estrutura de dados importante no pacote Java util.concurrent. Diferente das filas comuns, BlockingQueue fornece um método de acesso à fila thread-safe . A implementação de muitas classes de sincronização avançadas no pacote simultâneo é baseada em BlockingQueue.

BlockingQueue é geralmente usado no modelo produtor-consumidor.O produtor é o thread que adiciona elementos à fila e o consumidor é o thread que retira os elementos da fila. BlockingQueue é um contêiner para armazenar elementos .

A fila de bloqueio fornece quatro conjuntos diferentes de métodos para inserir, remover e verificar elementos:

método\método de tratamento Lançar uma exceção retornar valor especial continue bloqueando saída de tempo limite
método de inserção adicionar(e) oferta(e) colocar (e) oferta(e,tempo,unidade)
método de remoção remover() enquete() pegar() enquete (tempo, unidade)
Método de inspeção elemento() olhadinha() - -

Fila de bloqueio: BlockingQueue workQueue

  • Mantém objetos de tarefas executáveis ​​aguardando para serem executados.
  1. LinkedBlockingQueue
    1. Fila de bloqueio encadeada, a estrutura de dados subjacente é uma lista vinculada, o tamanho padrão é Integer.MAX_VALUE, o tamanho também é especificado
  2. ArrayBlockingQueue
    1. Fila de bloqueio de matriz, a estrutura de dados subjacente é uma matriz e o tamanho da fila precisa ser especificado.
  3. Fila Síncrona
    1. Fila síncrona, a capacidade interna é 0, cada operação put deve aguardar uma operação take e vice-versa.
  4. DelayQueue
    1. Fila de atraso, o elemento na fila só pode ser obtido da fila quando o tempo de atraso especificado expirar.
  5. PriorityBlockingQueue
    1. Fila de bloqueio ilimitada, a prioridade é determinada pelo objeto Compator passado pelo construtor.

O princípio da fila de bloqueio

A fila de bloqueio usa principalmente o controle de bloqueio multicondição (Condição) do bloqueio Lock.

O primeiro é o construtor, além de inicializar o tamanho da fila e se é um bloqueio justo, ele também inicializa dois monitores para o mesmo bloqueio (bloqueio), ou seja, notEmpty e notFull. As funções desses dois monitores podem atualmente ser entendidas simplesmente como agrupamento de marcas. Quando o thread estiver executando uma operação put, adicione o monitor notFull a ele para marcar o thread como produtor; quando o thread estiver executando uma operação take, adicione o monitor notFull para ele. Monitore notEmpty, marcando este thread como um consumidor.

Acho que você gosta

Origin blog.csdn.net/weixin_52315708/article/details/131521785
Recomendado
Clasificación