Princípio de implementação do pool de threads Java e análise de código-fonte

Princípio de implementação do pool de threads Java e análise de código-fonte


prefácio

Este artigo começou a ser escrito no final de novembro de 2019 e foi adiado até o final de 2020 e não foi concluído até o início de 2021.

O tempo é muito rápido e muito lento~!

Lembro-me vagamente que em outubro de 2019, quando um certo Dong deixou a startup e planejava uma entrevista para um emprego, ele me perguntou thread pool, você sabe? Então ele me enviou uma nota que escrevi em 2017 "Java Concurrent Programming Thread Pool Essential Knowledge Points", ele disse que é isso? Naquela época, eu pensei que havia tantos pools de threads~!

Em 9 de novembro de 2019, um certo Dong e eu pegamos o ônibus 815 da Dawang Road para Yanjiao. Naquela época, era só porque eu estava aprendendo alguns pontos de conhecimento relacionados ao multithreading, e só conversávamos no ônibus porque não havia nada para fazer. Naquela época, ele me fez algumas perguntas sobre o pool de threads. Acho que no pool de threads de trabalho normal, se você souber como usá-lo, poderá otimizar no máximo o número de threads principais. A discussão principal está relacionada à simultaneidade e bloqueios multiencadeados.

No final do ano, eu geralmente estava ocupado no trabalho, então raramente estudava sozinho. Depois de uma semana, lembrei-me da pergunta feita por Dong Dong: "Como os threads no pool de threads são gerados e como as tarefas esperar pela execução?".

Não estou muito claro sobre a lógica desta peça, então gravei temporariamente este item TODO e acho que vou estudá-lo quando tiver tempo. Como resultado, isso durou 2019 e 2020 e chegou diretamente a 2021.

Perdoe minha prolixidade, o ponto principal é que o período de tempo deste artigo é muito longo e deixou uma profunda impressão em mim. Eu tenho que falar sobre isso, vamos começar a trabalhar ~!

JDK1.8O código-fonte para analisar o design principal e a implementação do conjunto de encadeamentos Java.

Este artigo refere-se ao princípio de implementação do pool de encadeamentos Java e sua prática nos negócios da Meituan .

O princípio de implementação do pool de threads Java e sua prática nos negócios da Meituan se quiser saber mais sobre esses tipos de pools de threads, você pode ler o artigo sobre o princípio de implementação dos pools de threads Java e sua prática nos negócios da Meituan .

Se o leitor for um aluno que está fazendo desenvolvimento do lado do servidor, é altamente recomendável ler o princípio de implementação do conjunto de encadeamentos Java e sua prática nos negócios da Meituan .

Exterior

A aparência é principalmente alguns pontos que normalmente vemos ao usar o pool de threads.

  • relação de herança;
  • Construtor;
  • parâmetros no construtor;
  • Bloqueio de fila no construtor;
  • Criação de pool de threads;
  • Política de negação no construtor;

herança do pool de threads

ThreadPoolExecutor-uml.png

ThreadPoolExecutorA interface de nível superior implementada é que Executorna interface, Executoros usuários não precisam prestar atenção em como criar threads e como agendar threads para executar tarefas. Os usuários só precisam fornecer Runnableobjetos, enviar a lógica de operação da tarefa para o executor Executore Executoro framework completa a implantação de threads e a parte de execução de tarefas.

ExecutorServiceA interface adiciona alguns recursos:

  1. Estenda a capacidade de execução de tarefas, complementando Futuremétodos que podem ser gerados para uma ou um lote de tarefas assíncronas;
  2. Fornece métodos para gerenciar e controlar o pool de threads, como interromper a operação do pool de threads.

AbstractExecutorServiceÉ a classe abstrata da camada superior, que conecta o processo de execução de tarefas em série, garantindo que a implementação da camada inferior precise focar apenas em um método de execução de tarefas.

A classe de implementação de nível mais baixo ThreadPoolExecutorimplementa a parte de operação mais complexa:

  1. Um conjunto de número especificado de threads pode ser criado, gerenciado e reutilizado automaticamente, e a parte aplicável só precisa enviar a tarefa

  2. Segurança de thread, ThreadPoolExecutorestado interno, número de threads principais, threads não essenciais e outros atributos, uso extensivo de mecanismos de bloqueio CAS e AQS para evitar conflitos causados ​​por simultaneidade

  3. Fornece os conceitos de threads principais, filas de bloqueio de buffer, threads não essenciais e estratégias de descarte, que podem ser combinadas e usadas de acordo com os cenários de aplicativos reais

  4. Fornece beforeExecutee afterExecute()pode suportar a extensão da função do pool de threads

Construtor

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;
}
  • corePoolSize : O número de threads do núcleo no pool de threads. Geralmente, não importa se há tarefas ou não, elas sempre sobreviverão no pool de threads. Somente quando ThreadPoolExecutoro inallowCoreThreadTimeOut(boolean value) é definido como , os threads do núcleo ociosos terão um mecanismo de tempo limite. Se não houver novas tarefas dentro do tempo especificado trueQuando chegar, o thread principal também será encerrado e esse intervalo de tempo keepAliveTimeserá especificado .
  • maximumPoolSize : O número máximo de encadeamentos que o conjunto de encadeamentos pode acomodar. Quando o número de encadeamentos ativos atingir esse valor, as novas tarefas subseqüentes serão bloqueadas.
  • keepAliveTime : Controla o período de tempo limite quando o thread está ocioso. Se exceder, o thread será encerrado. Geralmente usado para threads não principais, ThreadPoolExecutorsomente quandoallowCoreThreadTimeOut(boolean value) o método in é definido como , ele também atua em threads principais.true
  • unit : usado para especificar keepAliveTimea unidade de tempo do parâmetro, TimeUnité um enumtipo de enumeração, comumente usados ​​são: TimeUnit.HOURS(小时), TimeUnit.MINUTES(分钟), TimeUnit.SECONDS(秒) e TimeUnit.MILLISECONDS(毫秒)etc.
  • workQueue : A fila de tarefas do pool de threads, execute(Runnable command)a tarefa será Runnablearmazenada na fila por meio do método do pool de threads.
  • threadFactory : fábrica de threads, que é uma interface usada para criar novos threads para o pool de threads.
  • handler : Estratégia de rejeição A chamada estratégia de rejeição refere-se à estratégia correspondente adotada pelo pool de threads para rejeitar a tarefa quando a tarefa é adicionada ao pool de threads.

Variáveis ​​de membros

/**
 * 任务阻塞队列 
 */
private final BlockingQueue<Runnable> workQueue; 
/**
 * 非公平的互斥锁(可重入锁)
 */
private final ReentrantLock mainLock = new ReentrantLock();
/**
 * 线程集合一个Worker对应一个线程,没有核心线程的说话,只有核心线程数
 */
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
 * 配合mainLock通过Condition能够更加精细的控制多线程的休眠与唤醒
 */
private final Condition termination = mainLock.newCondition();
/**
 * 线程池中线程数量曾经达到过的最大值。
 */
private int largestPoolSize;  
/**
 * 已完成任务数量
 */
private long completedTaskCount;
/**
 * ThreadFactory对象,用于创建线程。
 */
private volatile ThreadFactory threadFactory;  
/**
 * 拒绝策略的处理句柄
 * 现在默认提供了CallerRunsPolicy、AbortPolicy、DiscardOldestPolicy、DiscardPolicy
 */
private volatile RejectedExecutionHandler handler;
/**
 * 线程池维护线程(超过核心线程数)所允许的空闲时间
 */
private volatile long keepAliveTime;
/**
 * 允许线程池中的核心线程超时进行销毁
 */
private volatile boolean allowCoreThreadTimeOut;  
/**
 * 线程池维护线程的最小数量,哪怕是空闲的  
 */
private volatile int corePoolSize;
/**
 * 线程池维护的最大线程数量,线程数超过这个数量之后新提交的任务就需要进入阻塞队列
 */
private volatile int maximumPoolSize;

Criar um pool de threads

ExecutorsFornece métodos para obter vários pools de threads comumente usados:

  • Conjunto de threads de cache

newCachedThreadPoolÉ um pool de encadeamentos que cria novos encadeamentos conforme necessário, mas reutiliza os encadeamentos construídos anteriormente conforme eles se tornam disponíveis. Para programas que executam muitas tarefas assíncronas de curta duração, esses pools de encadeamento geralmente melhoram o desempenho do programa. A execute()chamada reutilizará um thread construído anteriormente (se um thread estiver disponível). Se nenhum encadeamento existente estiver disponível, um novo encadeamento será criado e adicionado ao pool. Encerra e remove threads que não foram usados ​​por 60 segundos do cache. Portanto, um pool de threads que permanece ocioso por muito tempo não usará nenhum recurso. Observe que pools de threads com propriedades semelhantes, mas detalhes diferentes (como parâmetros de tempo limite) podem ser criados usando o ThreadPoolExecutorconstrutor .

public static ExecutorService newCachedThreadPool() {
    
    
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                  60L, TimeUnit.SECONDS,
                  new SynchronousQueue<Runnable>());
}
  • pool de thread único

newSingleThreadExecutorCrie um pool de thread único, ou seja, o pool de threads tem apenas um thread funcionando e todas as tarefas são executadas em série. Se o único thread terminar de forma anormal, um novo thread o substituirá. Este pool de threads garante que a ordem de execução de todos tarefas é executada na ordem em que as tarefas são enviadas.

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

newFixedThreadPoolCrie um pool de threads de tamanho fixo e crie um thread toda vez que uma tarefa for enviada até que o thread atinja o tamanho máximo do pool de threads. Quando o pool de threads atingir o tamanho máximo, ele permanecerá inalterado. Se um thread terminar devido a uma execução anormal, o pool de threads adicionará um novo thread.

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    
    
  return new ThreadPoolExecutor(nThreads, nThreads,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>(),
                  threadFactory);
}
  • pool de thread único

newScheduledThreadPoolCrie um pool de encadeamentos de tamanho ilimitado que suporte tempo e execução periódica de tarefas.

public static ScheduledExecutorService newScheduledThreadPool(
    int corePoolSize, ThreadFactory threadFactory) {
  return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                   ThreadFactory threadFactory) {
  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
      new DelayedWorkQueue(), threadFactory);
}

Podemos ver que o método acima usa um total de DelayedWorkQueue, LinkedBlockingQueuee SynchronousQueue. Esta é a fila de bloqueio, um dos núcleos de encadeamento.

fila de bloqueio de tarefas

BlockingQueue.png

Geralmente é dividido em filas de envio direto, filas de tarefas limitadas, filas de tarefas ilimitadas e filas de tarefas prioritárias;

Fila Síncrona

1. Fila de envio direto : definida como SynchronousQueueuma fila, SynchronousQueueé especial BlockingQueue, não tem capacidade, será bloqueada toda vez que uma operação de inserção for executada e será despertada somente após a execução de outra operação de exclusão, caso contrário, a cada exclusão operação deve aguardar a operação de Inserção correspondente.

Usando SynchronousQueuea fila, as tarefas enviadas não serão salvas, e sempre serão enviadas para execução imediatamente. Se o número de threads usadas para executar a tarefa for menor que maximumPoolSize, tente criar um novo processo, se atingir maximumPoolSizeo valor máximo definido, ele handlerrejeitará a execução de acordo com a política definida. Portanto, as tarefas que você enviar dessa maneira não serão armazenadas em cache, mas serão executadas imediatamente. Nesse caso, você precisa ter uma avaliação precisa da simultaneidade do seu programa antes de definir o número apropriado, caso contrário, é fácil maximumPoolSize. política de rejeição será implementada;

ArrayBlockingQueue

2. Fila de tarefas limitada : Uma fila de tarefas limitada pode ser implementada ArrayBlockingQueueda seguinte forma:

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

Usando ArrayBlockingQueueuma fila de tarefas limitada, se uma nova tarefa precisar ser executada, o pool de threads criará um novo thread, até que o número de threads criados atinja , corePoolSizea nova tarefa será adicionada à fila de espera. Se a fila de espera estiver cheia, ou seja, exceder ArrayBlockingQueuea capacidade inicializada, continue criando threads até que o número de threads atinja o maximumPoolSizenúmero máximo de threads definido e, se for maior maximumPoolSize, execute a estratégia de rejeição. Nesse caso, o limite superior do número de threads está diretamente relacionado ao estado da fila de tarefas limitada. Se a capacidade inicial da fila limitada for grande ou o estado de sobrecarga não for atingido, o número de threads sempre será mantido abaixo de e. Caso contrário, quando a corePoolSizfila de tarefas estiver cheia, o número máximo de threads maximumPoolSizeserá limitado.

LinkedBlockingQueue

3. Fila de tarefas ilimitadas : A fila de tarefas ilimitadas pode ser implementada LinkedBlockingQueueda seguinte forma:

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

Usando uma fila de tarefas ilimitada, a fila de tarefas do pool de threads pode adicionar novas tarefas sem limite, e o número máximo de threads criados pelo pool de threads é o número que você corePoolSizedefiniu, ou seja, maximumPoolSizeeste parâmetro é inválido neste caso, mesmo se houver muitas tarefas não executadas armazenadas em cache na fila de tarefas. Quando o número de threads no pool de threads atingir corePoolSize, ele não aumentará mais; se novas tarefas forem adicionadas posteriormente, elas entrarão diretamente na fila e aguardarão. Ao usar este modo de fila de tarefas, certifique-se de prestar atenção à coordenação e controle entre o envio e o processamento de sua tarefa, caso contrário, haverá um problema de que as tarefas na fila continuarão a crescer devido à incapacidade de processá-las a tempo até que os recursos estão exaustos.

PriorityBlockingQueue

4. Fila de tarefas prioritárias : A fila de tarefas prioritárias é PriorityBlockingQueueimplementada por:

As tarefas serão reorganizadas e executadas de acordo com a prioridade, e o número de threads no pool de threads é sempre corePoolSize, ou seja, há apenas um.

PriorityBlockingQueueNa verdade, é uma fila ilimitada especial. Não importa quantas tarefas sejam adicionadas a ela, o número de threads criados pelo pool de threads não excederá o número máximo, mas outras filas geralmente processam tarefas de acordo com o primeiro em corePoolSizeprimeiro regra -out, e PriorityBlockingQueuea fila pode ser personalizada As regras são executadas sequencialmente de acordo com a ordem de prioridade das tarefas.

Na verdade, LinkedBlockingQueueo limite também pode ser definido e seu limite padrão é Integer.MAX_VALUE. Ao mesmo tempo, também suporta a configuração do tamanho da fila durante a construção.

política de rejeição

public interface RejectedExecutionHandler {
    
    
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

Quando Executorele foi fechado, ou seja, depois que o método foi executado executorService.shutdown(), ou Executorquando os limites limitados são usados ​​para a capacidade máxima de encadeamento e fila de trabalho e foram saturados. execute()Novas tarefas enviadas usando o método serão rejeitadas.No
caso acima, executeo método chamará seu métodoRejectedExecutionHandler .RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)

Política de rejeição padrão AbortPolicy

Também conhecida como política de rescisão, uma rejeição lança o runtime RejectedExecutionException. O lado comercial pode obter feedback oportuno sobre os resultados enviados para esta tarefa, capturando exceções.

public static class AbortPolicy implements RejectedExecutionHandler {
    
    
  public AbortPolicy() {
    
     }
  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
  }
}

CallerRunsPolicy

Ter um controle de feedback autônomo que permite que os remetentes executem tarefas de envio pode retardar o envio de novas tarefas. Neste caso, é necessário deixar que todas as tarefas sejam executadas.

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    
    
    public CallerRunsPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            r.run();
        }
    }
}

DiscardPolicy

Um manipulador para rejeitar tarefas, descartando tarefas silenciosamente. Usando esta estratégia, podemos não ser capazes de perceber o estado anormal do sistema. Use com cautela~!

public static class DiscardPolicy implements RejectedExecutionHandler {
    
    
    public DiscardPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    }
}

DiscardOldestPolicy

Descarte a primeira tarefa na fila e reenvie as tarefas rejeitadas. O uso dessa estratégia depende se o negócio precisa ser substituído pelo novo e pelo antigo, portanto, use-o com cuidado~!

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    
    
    public DiscardOldestPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

núcleo

Falei sobre a aparência do pool de threads anteriormente e depois falarei sobre seu núcleo .

Na verdade, o pool de threads constrói internamente um modelo de produtor-consumidor , que será desacoplado 线程dos 任务dois e não relacionado diretamente, de modo a armazenar bem as tarefas e reutilizar os threads.

A operação do pool de threads é dividida principalmente em duas partes: gerenciamento de tarefas e gerenciamento de threads.

A parte de gerenciamento de tarefas atua como um produtor. Quando uma tarefa é enviada, o pool de threads julgará o fluxo subsequente da tarefa:

  1. Candidate-se diretamente ao thread para executar a tarefa;
  2. Buffer na fila e aguarde a execução do thread;
  3. Recuse a tarefa.

A parte de gerenciamento da thread é o consumidor, que é mantido uniformemente no pool de threads, e a thread é alocada de acordo com a solicitação da tarefa. Após a thread executar a tarefa, ela continuará obtendo novas tarefas para executar. Finalmente, quando a thread não pode obter a tarefa, o segmento será reciclado.

A seguir, explicaremos detalhadamente o mecanismo de operação do pool de threads de acordo com as três partes a seguir:

  1. Como o pool de threads mantém seu próprio estado.
  2. Como o pool de encadeamentos gerencia tarefas.
  3. Como o pool de threads gerencia os threads.

O ciclo de vida do pool de threads

O estado de execução do pool de threads não é definido explicitamente pelo usuário, mas é mantido internamente junto com a execução do pool de threads.

O pool de threads usa internamente uma variável para manter dois valores: o status em execução ( runState) e o número de threads ( workerCount).

Na implementação específica, o pool de threads reúne a manutenção de dois parâmetros-chave, o estado de execução ( runState) e o número de threads ( ):workerCount

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctlEsse AtomicIntegertipo é um campo que controla o status de execução do pool de threads e o número de threads válidos no pool de threads.

Ele contém duas partes de informações ao mesmo tempo: o estado de execução do pool de threads ( runState) e o número de threads efetivos no pool de threads ( workerCount), os 3 bits superiores são salvos runStatee os 29 bits inferiores são salvos workerCounte o duas variáveis ​​não interferem entre si.

Utilizar uma variável para armazenar dois valores pode evitar inconsistências na hora de tomar decisões relevantes, não sendo necessário ocupar recursos de bloqueio para manter a consistência entre os dois. Também pode ser encontrado lendo o código-fonte do pool de threads que geralmente é necessário julgar o status de execução do pool de threads e o número de threads ao mesmo tempo. O pool de threads também fornece vários métodos para o usuário obter o status de execução atual e o número de threads do pool de threads. O método de operação de bit é usado aqui, que é muito mais rápido que a operação básica (PS: esse uso pode ser visto em muitos códigos-fonte).

O método de cálculo para obter o estado do ciclo de vida e obter o número de threads do pool de threads no pacote interno é mostrado no seguinte código:

private static final int COUNT_BITS = Integer.SIZE - 3;//32-3
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;//低29位都为1,高位都为0

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;//111
private static final int SHUTDOWN   =  0 << COUNT_BITS;//000
private static final int STOP       =  1 << COUNT_BITS;//001
private static final int TIDYING    =  2 << COUNT_BITS;//010
private static final int TERMINATED =  3 << COUNT_BITS;//011

// Packing and unpacking ctl
//计算当前运行状态,取高三位
private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
//计算当前线程数量,取低29位
private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
//通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

ThreadPoolExecutorExistem 5 estados de execução, a saber:

Estado operacional Descrição de status
CORRENDO Pode aceitar tarefas recém-enviadas e também pode processar tarefas na fila de bloqueio
DESLIGAR Não pode aceitar tarefas recém-enviadas, mas pode continuar a processar tarefas na fila de bloqueio
PARAR Ele não pode aceitar novas tarefas, nem pode processar tarefas na fila enquanto interrompe o thread da tarefa de processamento
ARRUMAR Todas as tarefas foram encerradas, workCount (número de threads efetivos) é 0
ENCERRADO Entre neste estado depois que o método terminado é executado

Instrução do pool de threads cycle.jpg

Mecanismo de Agendamento de Tarefas

O agendamento de tarefas é o principal ponto de entrada do pool de threads. Quando um usuário envia uma tarefa, como a tarefa será executada a seguir é determinada por esse estágio. Entender essa parte é equivalente a entender o mecanismo operacional principal do pool de encadeamentos.

Em primeiro lugar, o agendamento de todas as tarefas é executeconcluído pelo método. O trabalho realizado nesta parte é: verificar o status de execução atual do pool de threads , o número de threads em execução e a estratégia de execução e determinar o próximo processo de execução , seja para solicitar diretamente a execução do thread ou Buffered para a fila de execução ou rejeitar diretamente a tarefa . Seu processo de execução é o seguinte:

  1. Primeiro, verifique o status de execução do pool de threads, caso contrário RUNNING, rejeite-o diretamente e o pool de threads deve garantir que RUNNINGa tarefa seja executada em um determinado estado.
  2. Se workerCount < corePoolSize, um thread é criado e iniciado para executar a tarefa recém-enviada.
  3. Se workerCount >= corePoolSize, e a fila de bloqueio no conjunto de encadeamentos não estiver cheia, inclua a tarefa na fila de bloqueio.
  4. Se workerCount >= corePoolSize && workerCount < maximumPoolSize, e a fila de bloqueio no conjunto de encadeamentos estiver cheia, crie e inicie um encadeamento para executar a tarefa recém-enviada.
  5. Se workerCount >= maximumPoolSize, e a fila de bloqueio no pool de threads estiver cheia, a tarefa será processada de acordo com a estratégia de rejeição e o método de processamento padrão é lançar uma exceção diretamente.

Fluxograma de agendamento de tarefas.png

Em seguida, insira o tempo de análise do código-fonte~!

enviar tarefa

//AbstractExecutorService.java
public <T> Future<T> submit(Callable<T> task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
//ThreadPoolExecutor.java
public void execute(Runnable command) {
    
    
  if (command == null)
    throw new NullPointerException();
  int c = ctl.get();//获取ctl
  //检查当前核心线程数,是否小于核心线程数的大小限制
  if (workerCountOf(c) < corePoolSize) {
    
    
    //没有达到核心线程数的大小限制,那么添家核心线程执行该任务
    if (addWorker(command, true))
      return;
    //如果添加失败,刷新ctl值
    c = ctl.get();
  }
  //再次检查线程池的运行状态,将任务添加到等待队列中
  if (isRunning(c) && workQueue.offer(command)) {
    
    
    int recheck = ctl.get();//刷新ctl值
    //如果当前线程池的装不是运行状态,那么移除刚才添加的任务
    if (! isRunning(recheck) && remove(command))
      reject(command);//移除成功后,使用拒绝策略处理该任务;
    else if (workerCountOf(recheck) == 0)//当前工作线程数为0
      //线程池正在运行,或者移除任务失败。
      //添加一个非核心线程,并不指定该线程的运行任务。
      //等线程创建完成之后,会从等待队列中获取任务执行。
      addWorker(null, false);
  } 
  //逻辑到这里说明线程池已经不是RUNNING状态,或者等待队列已满,需要创建一个新的非核心线程执行该任务;
  //如果创建失败,那么非核心线程已满,使用拒绝策略处理该任务;
  else if (!addWorker(command, false))
    reject(command);
}

Adicionar threads de trabalho e executar tarefas

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
  Worker(Runnable firstTask) {
    
    
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;//初始化的任务,可以为null
    this.thread = getThreadFactory().newThread(this);//Worker持有的线程
  }
  /**部分代码省略*/
	public void run() {
    
    
  	runWorker(this);
	}
}

Adicionar trabalhadores e executar tarefas : a coisa toda é criar Workere encontrar uma correspondência para isso Runnable.

addwork.png

Adicionar thread de trabalho

A adição de threads é feita através do método no pool de threads addWorker. A função deste método é adicionar um thread. Este método não considera em qual estágio o pool de threads adiciona o thread. Essa estratégia de alocação de threads é concluída na etapa anterior. Este A etapa é apenas concluir o aumento do thread, executá-lo e, finalmente, retornar o resultado se foi bem-sucedido ou não.

addWorkerO método possui dois parâmetros: firstTask, core.

firstTaskO parâmetro é usado para especificar a primeira tarefa executada pelo thread recém-adicionado, que pode estar vazio;

coreO parâmetro truesignifica que ao adicionar um novo encadeamento, ele julgará se o número de encadeamentos ativos atuais é menor que corePoolSize, falsee isso significa que ele precisa julgar se o número de encadeamentos ativos atuais é menor do que antes de adicionar um novo encadeamento maximumPoolSize.

addwork2.png

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    retry://breakcontinue的跳出标签
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取当前线程池的状态;
        /**
         * 1、如果当前的线程池状态不是RUNNING
         * 2、当前线程池是RUNNING而且没有添加新任务,而且等待队列不为空。这种情况下是需要创建执行线程的。
         * 所以满足1,但不满足2就创建执行线程失败,返回false。
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;
        /**进入内层循环 */
        for (;;) {
    
    
            int wc = workerCountOf(c);//获取当前执行线程的数量
            /**
             * 1、工作线程数量大于或等于计数器的最大阈值,那么创建执行线程失败,返回false。
             * 2、如果当前创建的核心线程,那么工作线程数大于corePoolSize的话,创建执行线程失败,返回false。
             * 3、如果当前创建的是非核心线程,那么工作线程数大于maximumPoolSize的话,创建执行线程失败,返回false。
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //用CAS操作让线程数加1,如果成功跳出整个循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)//线程状态前后不一样,重新执行外循环
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
            //如果CAS操作由于工作线程数的增加失败,那么重新进行内循环
        }
    }
    /**就现在,线程数已经增加了。但是真正的线程对象还没有创建出来。*/
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
    
    
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();//加锁
            try {
    
    
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                /**
                 * 再次检查线程池的运行状态
                 * 1、如果是RUNNING状态,那么可以创建;
                 * 2、如果是SHUTDOWN状态,但没有执行线程,可以创建(创建后执行等待队列中的任务)
                 */
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
    
    
                    //检测该线程是否已经开启
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);//将改工作线程添加到集合中
                    int s = workers.size();
                    if (s > largestPoolSize)//更新线程池的运行时的最大线程数
                        largestPoolSize = s;
                    workerAdded = true;//标识工作线程添加成功
                }
            } finally {
    
    //释放锁
                mainLock.unlock();
            }
            if (workerAdded) {
    
    //如果工作线程添加成功,那么开启线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    
    
        //如果工作线程添加失败,那么进行失败处理
        //将已经增加的线程数减少,将添加到集合的工作线程删除
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

realizar tarefas

Vimos na seção de adição de um thread de trabalho que após a adição ser bem-sucedida, o thread será iniciado para executar a tarefa.

runwork.png

final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //解锁,允许中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
        //如果当前的工作线程已经有执行任务,或者可以从等待队列中获取到执行任务
        //getTask获取任务时候会进行阻塞
        while (task != null || (task = getTask()) != null) {
    
    
            w.lock();//开始执行,上锁
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            //判断线程是否需要中断
            //如果线程池状态是否为STOP\TIDYING\TERMINATED,同时当前线程没有被中断那么将当前线程进行中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
    
    
                beforeExecute(wt, task);
                Throwable thrown = null;//异常处理
                try {
    
    
                    task.run();
                } catch (RuntimeException x) {
    
    
                    thrown = x; throw x;
                } catch (Error x) {
    
    
                    thrown = x; throw x;
                } catch (Throwable x) {
    
    
                    thrown = x; throw new Error(x);
                } finally {
    
    
                    afterExecute(task, thrown);
                }
            } finally {
    
    
                task = null;//工作线程的当前任务置空
                w.completedTasks++;//当前工作线程执行完成的线程数+1
                w.unlock();//执行完成解锁
            }
        }
        completedAbruptly = false;//完成了所有任务,正常退出
    } finally {
    
    //执行工作线程的退出操作
        processWorkerExit(w, completedAbruptly);
    }
}

O thread de trabalho recebe a tarefa

getTask.jpg

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取线程池状态

        // Check if queue empty only if necessary.
        /**
         * 1、rs为STOP\TIDYING\TERMINATED,标识无法继续执行任务
         * 2、等待队列中没有任务可以被执行
         * 工作线程数量减一
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);//获取工作线程数量
        // Are workers subject to culling?
        //如果允许核心线程超时,或者当前工作线程数量大于核心线程数量。标识需要进行超时检测
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        /**
         * 1、如果当前工作线程数是否大于线程池可允许的最大工作线程数(maximumPoolSize可以动态设置)
         * ,或者当前需要进行超时控制并且上次从等待队列中获取执行任务发生了超时。
         * 2、如果当前不是唯一的线程,并且等待队列中没有需要执行的任务。
         * 这两种情况下一起存在就表示,工作线程发生了超时需要回收,所以对线程数进行-1;
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
    
    
            if (compareAndDecrementWorkerCount(c))//线程数量减少成功,否则重新执行本次循环
                return null;
            continue;
        }
        try {
    
    
            //如果设置有超时,那么设定超时时间。否则进行无限的阻塞等待执行任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;//获取超时,设置标记
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}

saída do thread de trabalho

A destruição de threads no pool de threads depende da reciclagem automática da JVM . JVM. Quando o pool de threads decide quais threads precisam ser reciclados, apenas você precisa remover sua referência. WorkerDepois de criada, a votação será realizada continuamente e, em seguida, a tarefa será adquirida para execução. O thread principal pode esperar indefinidamente para adquirir a tarefa e o thread não principal deve adquirir a tarefa dentro de um tempo limitado. Quando Workera tarefa não pode ser obtida, ou seja, a tarefa obtida está vazia, o loop terminará e Workereliminará ativamente sua própria referência no pool de threads.

processWorkerExit.png

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
    //completedAbruptly为true,标识该工作线程执行出现了异常,将工作线程数减一
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
    //否则标识该工作线程为正常结束,这种情况下getTask方法中已经对工作线程进行了减一
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        completedTaskCount += w.completedTasks;//更新线程池的,线程执行完成数量
        workers.remove(w);//工作线程容器移除该工作线程
    } finally {
    
    
        mainLock.unlock();//解锁
    }
    //尝试结束线程池
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
    
    //如果当前线程池的运行状态是RUNNING\SHUTDOWN
        if (!completedAbruptly) {
    
    //如果该工作线程为正常结束
            /**
             * 判断当前需要的最少的核心线程数(如果允许核心线程超时,那么最小的核心线程数为0,否则为corePoolSize)
             */
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            //如果允许核心线程超时,而且等待队列不为空,那么工作线程的最小值为1,否则为0。
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            //当前工作线程数,是否满足最先的核心线程数
            if (workerCountOf(c) >= min)
                //如果满足那么直接return
                return; // replacement not needed
        }
        //如果是异常结束,或者当前线程数不满足最小的核心线程数,那么添加一个非核心线程
        //核心线程和非核心线程没有什么不同,只是在创建的时候判断逻辑不同
        addWorker(null, false);
    }
}

necessidades especiais

Monitoramento do pool de threads

Monitore por meio dos parâmetros fornecidos pelo pool de encadeamentos. Existem alguns atributos no pool de threads que podem ser usados ​​ao monitorar o pool de threads

  • getTaskCount: O número total de tarefas executadas e não executadas no conjunto de encadeamentos;
  • getCompletedTaskCount: O número de tarefas concluídas pelo pool de encadeamentos, o valor é menor ou igual a taskCount;
  • getLargestPoolSize: O número máximo de encadeamentos já criados pelo pool de encadeamentos. Através desses dados, podemos saber se o pool de threads está cheio, ou seja, foi atingido maximumPoolSize;
  • getPoolSize: O número atual de encadeamentos no conjunto de encadeamentos;
  • getActiveCount: O número de encadeamentos atualmente executando tarefas no conjunto de encadeamentos.

Ajuste dinamicamente o tamanho do pool de threads

JDKPermite que o usuário do pool de threads ThreadPoolExecutordefina dinamicamente a política principal do pool de threads por meio da instância, setCorePoolSizecomo um exemplo do método;

Depois que o usuário do pool de threads de tempo de execução chamar essa configuração de método corePoolSize, o pool de threads substituirá diretamente o corePoolSizevalor original e adotará diferentes estratégias de processamento com base na comparação entre o valor atual e o valor original.

Caso o valor atual seja menor que o número atual de threads de trabalho, significa que existem workerthreads redundantes. Neste momento, uma solicitação de interrupção será iniciada para o idlethread atual workerpara realizar a reciclagem, e os redundantes também serão reciclado na próxima vez; pois o valor atual é maior que o valor original e o workeratual para executar as tarefas da fila (PS: o estado é o estado após a rosca libera o travamento, pois fica travado durante a operação).idelworkeridelworker

setCorePoolSize.png

public void setCorePoolSize(int corePoolSize) {
    
    
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    //计算增量
    int delta = corePoolSize - this.corePoolSize;
    //覆盖原有的corePoolSize
    this.corePoolSize = corePoolSize;
    //如果当前的工作线程数量大于线程池的最大可运行核心线程数量,那么进行中断工作线程处理
    if (workerCountOf(ctl.get()) > corePoolSize)
        interruptIdleWorkers();
    else if (delta > 0) {
    
    //如果增量大于0
        // We don't really know how many new threads are "needed".
        // As a heuristic, prestart enough new workers (up to new
        // core size) to handle the current number of tasks in
        // queue, but stop if queue becomes empty while doing so.
        //等待队列非空,获取等待任务和增量的最小值
        int k = Math.min(delta, workQueue.size());
        //循环创建核心工作线程执行等待队列中的任务
        while (k-- > 0 && addWorker(null, true)) {
    
    
            if (workQueue.isEmpty())
                break;
        }
    }
}
private void interruptIdleWorkers() {
    
    
    interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        //遍历工作线程的集合
        for (Worker w : workers) {
    
    
            Thread t = w.thread;
            //如果当前线程没有被中断,而且能获取到锁,那么尝试进行中断,最后释放锁
            if (!t.isInterrupted() && w.tryLock()) {
    
    
                try {
    
    
                    t.interrupt();
                } catch (SecurityException ignore) {
    
    
                } finally {
    
    
                    w.unlock();
                }
            }
            //是否仅仅中断一个工作线程
            if (onlyOne)
                break;
        }
    } finally {
    
    //释放锁
        mainLock.unlock();
    }
}

Encerre graciosamente o pool de threads

Também pode ser visto no diagrama "Thread Pool Statement Cycle" que quando executamos ThreadPoolExecutor#shutdowno método o estado do thread pool mudará de RUNNING para SHUTDOWN . ThreadPoolExecutor#shutdownNowApós a chamada, o status do pool de encadeamentos mudará de RUNNING para STOP .

desligar

Pare de aceitar novas tarefas e as tarefas originais continuam a ser executadas

  1. Parar de receber novas tarefas de envio;
  2. As tarefas que foram submetidas (incluindo as que estão rodando e as que estão esperando na fila) continuarão sendo executadas;
  3. Aguarde até que a etapa 2 seja concluída antes de realmente parar;
public void shutdown() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查权限
        advanceRunState(SHUTDOWN);// 设置线程池状态
        interruptIdleWorkers();// 中断空闲线程
        // 钩子函数,主要用于清理一些资源
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
}

shutdownO método primeiro bloqueia e, em seguida, verifica primeiro o status da instalação do sistema. Em seguida, o estado do pool de threads será alterado para SHUTDOWN , após o qual o pool de threads não aceitará mais novas tarefas enviadas. Neste momento, se você continuar enviando tarefas para o pool de threads, a política de rejeição do pool de threads será usada para responder, que será usada por padrão e uma exceçãoThreadPoolExecutor.AbortPolicy será lançada .RejectedExecutionException

interruptIdleWorkersO método é descrito no código-fonte na seção de ajuste dinâmico do tamanho do pool de encadeamentos . Ele interromperá apenas os encadeamentos ociosos e não interromperá os encadeamentos que estão executando tarefas. Os encadeamentos ociosos serão bloqueados na fila de bloqueio do pool de encadeamentos.

desligar agora

Pare de aceitar novas tarefas e as tarefas originais param de ser executadas

  1. Igual shutdown() a , primeiro pare de receber novas submittarefas;
  2. Ignora tarefas esperando na fila;
  3. Tentativa de interrupção da tarefa em execução interrupt;
  4. Retorna uma lista de tarefas não executadas;
public List<Runnable> shutdownNow() {
    
    
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查状态
        advanceRunState(STOP);// 将线程池状态变为 STOP
        interruptWorkers();// 中断所有线程,包括工作线程以及空闲线程
        tasks = drainQueue();// 丢弃工作队列中存量任务
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private void interruptWorkers() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        for (Worker w : workers)
            //如果工作线程已经开始,那么调用interrupt进行中断
            w.interruptIfStarted();
    } finally {
    
    
        mainLock.unlock();
    }
}
private List<Runnable> drainQueue() {
    
    
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    //从此队列中删除所有可用的元素,并将它们添加到给定的集合中。
    q.drainTo(taskList);
    //如果队列是DelayQueue或其他类型的队列,而poll或drainTo可能无法删除某些元素,则会将它们逐个删除。
    if (!q.isEmpty()) {
    
    
        for (Runnable r : q.toArray(new Runnable[0])) {
    
    
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

shutdownNowO método de tentar encerrar o thread é realizado chamando Thread.interrupt()o método , que tem efeito limitado.Se não houver aplicativos como sleep, wait, , bloqueio de tempo no thread, o método não poderá interromper o thread atual. Portanto, isso não significa que o pool de threads poderá sair imediatamente e pode ter que esperar que todas as tarefas em execução sejam concluídas antes de sair. Mas na maioria das vezes é possível desistir imediatamente.Conditioninterrupt()shutdownNow()

Mecanismo de interrupção de encadeamento: thread#interruptapenas definir um sinalizador de interrupção não interromperá imediatamente os encadeamentos normais. Se você quiser que a interrupção entre em vigor imediatamente, você deve chamar o thread para Thread.interrupted()julgar o status de interrupção do thread. Para um thread bloqueado, quando a interrupção é chamada, o thread sairá imediatamente do estado bloqueado e lançará InterruptedExceptionuma exceção . Portanto, para threads bloqueados, InterruptedExceptionas exceções .

awaitTermination

O pool de threads shutdowne shutdownNowo método não aguardarão ativamente o final da tarefa de execução.Se você precisar esperar até o final da execução da tarefa do pool de threads, será necessário chamar Aguardar awaitTerminationativamente final da chamada da tarefa.

  • Aguarde até que todas as tarefas enviadas (incluindo execução e espera na fila) sejam executadas;
  • Aguarde até que o tempo limite expire;
  • o thread é interrompido, lança InterruptedException;

Se a execução da tarefa do pool de threads terminar, awaitTerminationo método retornará true, caso contrário, retornará quando o tempo de espera exceder o tempo especificado false.

// 关闭线程池的钩子函数
private static void shutdown(ExecutorService executorService) {
    
    
    // 第一步:使新任务无法提交
    executorService.shutdown();
    try {
    
    
        // 第二步:等待未完成任务结束
        if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
             // 第三步:取消当前执行的任务
            executorService.shutdownNow();
            // 第四步:等待任务取消的响应
            if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
                System.err.println("Thread pool did not terminate");
            }
        }
    } catch(InterruptedException ie) {
    
    
        // 第五步:出现异常后,重新取消当前执行的任务
        executorService.shutdownNow();
        Thread.currentThread().interrupt(); // 设置本线程中断状态
    }
}

outro

Eu sinto que o conteúdo é tanto que não consigo terminar~!

Ao falar sobre o pool de encadeamentos, temos que falar sobre operações simultâneas multiencadeadas, 同步, 异步, , , e outros pontos de conhecimento necessários para o controle de simultaneidade CSA.AQS公平锁和非公平锁可重入锁和非可重入锁

Raramente é usado no trabalho diário.Você tem uma estrutura de conhecimento sistemático? Isso leva ao esquecimento depois de aprender muito, e depois aprender e esquecer novamente.

Espero ter a oportunidade de aprender e compartilhar passo a passo no futuro.

O artigo está todo contado aqui, se você tiver outras necessidades de comunicação, pode deixar uma mensagem~!

Acho que você gosta

Origin blog.csdn.net/stven_king/article/details/113870331
Recomendado
Clasificación