Pool de threads de simultaneidade Java

Autor original: Matrix Hai Zi 

Endereço original: programação simultânea Java: o uso de pool de threads

 

No artigo anterior, criamos um thread quando usamos um thread. Isso é muito fácil de implementar, mas haverá um problema:

  Se houver um grande número de threads simultâneos e cada thread executar uma tarefa de curto prazo e terminar, a criação frequente de threads reduzirá muito a eficiência do sistema, porque leva tempo para criar e destruir threads com frequência.

  Então, existe uma maneira de tornar os threads reutilizáveis, ou seja, após a execução de uma tarefa, ela não será destruída, mas poderá continuar executando outras tarefas?

  Em Java, esse efeito pode ser obtido por meio de um pool de threads. Hoje vamos explicar o pool de threads em Java em detalhes. Primeiro, começamos com os métodos na classe ThreadPoolExecutor central e, em seguida, explicamos seu princípio de implementação, em seguida, damos um exemplo de seu uso e, finalmente, discutimos como configurá-lo razoavelmente. do pool de threads.

1. Classe ThreadPoolExecutor em Java

  A classe java.uitl.concurrent.ThreadPoolExecutor é a classe principal no pool de threads, portanto, se quiser entender completamente o pool de threads em Java, você deve primeiro entender essa classe. Vamos dar uma olhada no código-fonte de implementação específico da classe ThreadPoolExecutor.

  Quatro métodos de construção são fornecidos na classe ThreadPoolExecutor:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

Como pode ser visto no código acima, ThreadPoolExecutor herda a classe AbstractExecutorService e fornece quatro construtores. Na verdade, ao observar a implementação específica do código-fonte de cada construtor, verifica-se que os três primeiros construtores são os quartos construtores chamados de trabalho de inicialização executado pelo dispositivo.

   O seguinte explica o significado de cada parâmetro no construtor:

  • corePoolSize: O tamanho do pool principal.Este parâmetro tem uma ótima relação com o princípio de implementação do pool de threads descrito posteriormente. Depois que o pool de threads é criado, por padrão, não há thread no pool. Em vez disso, ele espera que as tarefas cheguem antes de criar threads para realizar as tarefas, a menos que o método prestartAllCoreThreads () ou prestartCoreThread () seja chamado. métodos Como você pode ver pelo nome, significa threads pré-criados, ou seja, threads corePoolSize ou um thread são criados antes que nenhuma tarefa chegue. Por padrão, depois que o pool de threads é criado, o número de threads no pool de threads é 0. Quando uma tarefa chega, uma thread será criada para executar a tarefa. Quando o número de threads no pool de threads atingir corePoolSize, alcançar Colocar as tarefas na fila de cache;
  • maximumPoolSize: o número máximo de threads no pool de threads, este parâmetro também é um parâmetro muito importante, ele indica o número máximo de threads que podem ser criados no pool de threads;
  • keepAliveTime: indica quanto tempo o thread se mantém no máximo quando não há execução da tarefa será encerrada. Por padrão, keepAliveTime funcionará apenas quando o número de threads no pool de threads for maior que corePoolSize, até que o número de threads no pool de threads não seja maior que corePoolSize, ou seja, quando o número de threads no pool de threads for maior do que corePoolSize, se um encadeamento estiver inativo Quando o tempo atingir keepAliveTime, ele será encerrado até que o número de encadeamentos no pool de encadeamentos não exceda corePoolSize. Mas se o método allowCoreThreadTimeOut (boolean) for chamado, quando o número de threads no pool de threads não for maior que corePoolSize, o parâmetro keepAliveTime também funcionará até que o número de threads no pool de threads seja 0;
  • unidade: a unidade de tempo do parâmetro keepAliveTime. Existem 7 valores. Existem 7 propriedades estáticas na classe TimeUnit:
TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
  • workQueue: uma fila de bloqueio usada para armazenar tarefas aguardando para serem executadas. A escolha deste parâmetro também é muito importante e terá um impacto significativo no processo de execução do pool de threads. De modo geral, existem várias opções para a fila de bloqueio aqui :
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

  ArrayBlockingQueue e PriorityBlockingQueue são usados ​​com menos frequência, e LinkedBlockingQueue e Synchronous são geralmente usados. A estratégia de enfileiramento do pool de threads está relacionada ao BlockingQueue.

  • threadFactory: fábrica de threads, usada principalmente para criar threads;
  • handler: indica a estratégia quando a tarefa é recusada para ser processada, com os quatro valores a seguir:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

   O relacionamento entre a configuração de parâmetros específicos e o pool de threads será descrito na próxima seção.

  A partir do código da classe ThreadPoolExecutor fornecido acima, podemos saber que ThreadPoolExecutor herda AbstractExecutorService. Vamos dar uma olhada na implementação de AbstractExecutorService:

public abstract class AbstractExecutorService implements ExecutorService {
 
     
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
    public Future<?> submit(Runnable task) {};
    public <T> Future<T> submit(Runnable task, T result) { };
    public <T> Future<T> submit(Callable<T> task) { };
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                            boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
    };
}

AbstractExecutorService é uma classe abstrata que implementa a interface ExecutorService.

  Em seguida, examinamos a implementação da interface ExecutorService:

public interface ExecutorService extends Executor {
 
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
 
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

E ExecutorService herda a interface do Executor, vamos dar uma olhada na implementação da interface do Executor:

public interface Executor {
    void execute(Runnable command);
}

   Neste ponto, todos devem entender a relação entre ThreadPoolExecutor, AbstractExecutorService, ExecutorService e Executor .

  Executor é uma interface de nível superior, na qual apenas um método execute (Runnable) é declarado, o valor de retorno é nulo e o parâmetro é do tipo Runnable. Pode ser entendido pelo significado literal que é usado para executar a tarefa Transmitido;

  Em seguida, a interface ExecutorService herda a interface Executor e declara alguns métodos: submit, invokeAll, invokeAny, shutDown, etc .;

  A classe abstrata AbstractExecutorService implementa a interface ExecutorService e basicamente implementa todos os métodos declarados no ExecutorService;

  Então ThreadPoolExecutor herda a classe AbstractExecutorService.

  Existem vários métodos muito importantes na classe ThreadPoolExecutor:

execute()
submit()
shutdown()
shutdownNow()

   O método execute () é na verdade um método declarado em Executor. É implementado em ThreadPoolExecutor. Este método é o método principal de ThreadPoolExecutor. Por meio desse método, uma tarefa pode ser enviada ao pool de threads e executada pelo pool de threads.

  O método submit () é um método declarado em ExecutorService. Ele foi implementado em AbstractExecutorService. Não é reescrito em ThreadPoolExecutor. Este método também é usado para enviar tarefas para o pool de threads, mas ele e o método execute () são diferentes, pode retornar o resultado da execução da tarefa, olhe para a implementação do método submit (), você descobrirá que é na verdade o método execute () chamado, mas usa Future para obter o resultado da execução da tarefa (o conteúdo relacionado ao futuro será o próximo artigo).

  shutdown () e shutdownNow () são usados ​​para fechar o pool de threads.

  Existem muitos outros métodos:

  Por exemplo: getQueue (), getPoolSize (), getActiveCount (), getCompletedTaskCount () e outros métodos para obter atributos relacionados ao pool de threads.Os amigos interessados ​​podem consultar a API por conta própria.

2. Análise aprofundada do princípio de implementação do pool de threads

  Na seção anterior, apresentamos ThreadPoolExecutor a partir de um nível macro. Vamos analisar o princípio de implementação específico do pool de threads em profundidade e explicar os seguintes aspectos: estado do pool de threads, execução de tarefas, inicialização de threads no pool de threads, cache de tarefas Queue e enfileiramento estratégia, estratégia de rejeição de tarefa, fechamento do pool de threads, ajuste dinâmico da capacidade do pool de threads

1. Status do pool de threads

  Uma variável volátil é definida em ThreadPoolExecutor e várias variáveis ​​finais estáticas são definidas para representar os vários estados do pool de threads:

//表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
volatile int runState;

//下面的几个static final变量表示runState可能的几个取值。

//当创建线程池后,初始时,线程池处于RUNNING状态;
static final int RUNNING    = 0;

//如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
static final int SHUTDOWN   = 1;

//如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
static final int STOP       = 2;

//当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
static final int TERMINATED = 3;

2. Execução de tarefa

  Antes de compreender todo o processo, desde o envio da tarefa ao pool de threads até a conclusão da execução da tarefa, vamos dar uma olhada em algumas outras variáveis ​​de membro importantes na classe ThreadPoolExecutor:

private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
 
private volatile long  keepAliveTime;    //线程存活时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
 
private volatile int   poolSize;       //线程池中当前的线程数
 
private volatile RejectedExecutionHandler handler; //任务拒绝策略
 
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
 
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
 
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

A função de cada variável foi marcada, aqui vamos nos concentrar em explicar as três variáveis ​​corePoolSize, maximumPoolSize e maiorPoolSize.

corePoolSize é traduzido em tamanho do pool de núcleo em muitos lugares.Na verdade, meu entendimento é o tamanho do pool de threads. Para dar um exemplo simples: se há uma fábrica, há 10 trabalhadores na fábrica, e cada trabalhador pode fazer apenas uma tarefa por vez. Portanto, enquanto houver trabalhadores entre os 10 trabalhadores que estão ociosos, as tarefas que vierem serão atribuídas aos trabalhadores ociosos; quando 10 trabalhadores tiverem tarefas a fazer, se ainda houver tarefas, as tarefas serão enfileiradas; se você O número de novas tarefas está crescendo a uma taxa muito mais rápida do que os trabalhadores podem fazer. Neste momento, o supervisor da fábrica pode querer remediar medidas, como recrutar 4 trabalhadores temporários novamente; em seguida, atribuir tarefas a esses 4 trabalhadores temporários; se Falar da velocidade da tarefa de 14 trabalhadores ainda não é suficiente, neste momento o supervisor da fábrica pode ter que pensar em não aceitar novas tarefas ou abandonar algumas das tarefas anteriores. Quando alguns desses 14 trabalhadores são gratuitos e a taxa de crescimento de novas tarefas é relativamente lenta, o supervisor da fábrica pode considerar a possibilidade de despedir 4 trabalhadores temporários e manter apenas os 10 trabalhadores originais. Afinal, custa dinheiro contratar trabalhadores adicionais.

O corePoolSize neste exemplo é 10 e o maximumPoolSize é 14 (10 + 4). Em outras palavras, corePoolSize é o tamanho do pool de threads.Em minha opinião, o maximumPoolSize é uma solução para o pool de threads, ou seja, uma solução quando o volume da tarefa fica repentinamente muito grande. No entanto, para facilitar a compreensão, corePoolSize será convertido em tamanho do pool principal posteriormente neste artigo.

maiorPoolSize é apenas uma variável usada para gravação, usada para registrar o maior número de threads já no pool de threads, e não tem nada a ver com a capacidade do pool de threads.

Vamos passar ao tópico e dar uma olhada no processo da tarefa desde o envio até a execução final.

Na classe ThreadPoolExecutor, o método de envio de tarefa principal é o método execute (). Embora a tarefa possa ser enviada por meio de submit, na verdade, a chamada final no método submit é o método execute (), então só precisamos estudar o Método execute () O princípio de realização pode ser:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

O código acima pode não parecer tão fácil de entender, vamos explicá-lo frase por frase:

Primeiro, determine se o comando de tarefa enviado é nulo; se for nulo, uma exceção de ponteiro nulo será lançada;

Seguida por esta frase, esta frase deve ser entendida:

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))

Por ser um operador ou condicional, primeiro calcule o valor da primeira metade.Se o número atual de threads no pool de threads não for menor que o tamanho do pool principal, ele entrará diretamente no seguinte bloco de instrução if.

Se o número atual de threads no pool de threads for menor que o tamanho do pool de núcleos, execute a segunda metade, que é executar

addIfUnderCorePoolSize(command)

  Se o método addIfUnderCorePoolSize retornar falso após a execução, continue a executar o seguinte bloco de instrução if, caso contrário, todo o método será executado diretamente.

  Se o método addIfUnderCorePoolSize retornar falso após a execução, continue a julgar:

if (runState == RUNNING && workQueue.offer(command))

Se o pool de threads atual estiver no estado RUNNING, a tarefa será colocada na fila do cache de tarefas; se o pool de threads atual não estiver no estado RUNNING ou a tarefa falhar ao ser colocada na fila de cache, execute:

addIfUnderMaximumPoolSize(command)

  Se o método addIfUnderMaximumPoolSize falhar, o método rejeitar () será executado para rejeitar a tarefa.

  Voltar para a frente:

if (runState == RUNNING && workQueue.offer(command))

   A execução desta frase, se for dito que o pool de threads atual está no estado RUNNING e a tarefa foi colocada com sucesso na fila do cache de tarefas, então continue a julgar:

if (runState != RUNNING || poolSize == 0)

   Esta frase é uma medida de emergência para evitar que outras threads chamem repentinamente o método shutdown ou shutdownNow para fechar o pool de threads enquanto adicionam esta tarefa à fila de cache de tarefas. Se sim, execute:

ensureQueuedTaskHandled(command)

   Para tratamento de emergência, pode ser visto a partir do nome para garantir que as tarefas adicionadas à fila do cache de tarefas sejam processadas.

  Em seguida, examinamos a implementação de dois métodos principais: addIfUnderCorePoolSize e addIfUnderMaximumPoolSize:

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < corePoolSize && runState == RUNNING)
            t = addThread(firstTask);        //创建线程去执行firstTask任务   
        } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

Esta é a implementação específica do método addIfUnderCorePoolSize A partir do nome, pode-se perceber que sua intenção é executar o método quando o tamanho for menor que o tamanho do núcleo. Vejamos sua implementação específica. Primeiro, o bloqueio é adquirido, porque este lugar envolve a mudança do estado do pool de threads. Primeiro, use a instrução if para determinar se o número de threads no pool de threads atual é menor que o pool principal size. Alguns amigos podem ter dúvidas: Não foi julgado no método ()? O método addIfUnderCorePoolSize será executado apenas quando o número atual de threads no pool de threads for menor do que o tamanho do pool principal. Por que este lugar continua julgar? O motivo é muito simples. Não há bloqueio no processo de julgamento anterior, portanto, poolSize pode ser menor que corePoolSize quando o método de execução julga e, após a conclusão do julgamento, as tarefas são submetidas ao pool de threads em outros threads, que podem fazer com que o poolSize não seja menor que corePoolSize Portanto, você precisa continuar a julgar neste lugar. Em seguida, determine se o status do pool de threads é RUNNING, o motivo também é muito simples, pois é possível chamar o método shutdown ou shutdownNow em outras threads. Então execute

t = addThread(firstTask);

   Este método também é muito crítico.O parâmetro passado é a tarefa enviada e o valor de retorno é do tipo Thread. Em seguida, julgue se t está vazio. Se estiver vazio, indica que a criação do thread falhou (ou seja, poolSize> = corePoolSize ou runState não é igual a RUNNING), caso contrário, o método t.start () é chamado para iniciar o thread.

  Vamos dar uma olhada na implementação do método addThread:

private Thread addThread(Runnable firstTask) {
    Worker w = new Worker(firstTask);
    Thread t = threadFactory.newThread(w);  //创建一个线程,执行任务   
    if (t != null) {
        w.thread = t;            //将创建的线程的引用赋值为w的成员变量       
        workers.add(w);
        int nt = ++poolSize;     //当前线程数加1       
        if (nt > largestPoolSize)
            largestPoolSize = nt;
    }
    return t;
}

   No método addThread, primeiro crie um objeto Worker com a tarefa enviada, depois chame o thread factory threadFactory para criar um novo thread t e atribua a referência do thread t ao thread de variável de membro do objeto Worker e, em seguida, passe os workers. add (w) Adiciona o objeto Worker ao conjunto de trabalho.

  Vamos dar uma olhada na implementação da classe Worker:

private final class Worker implements Runnable {
    private final ReentrantLock runLock = new ReentrantLock();
    private Runnable firstTask;
    volatile long completedTasks;
    Thread thread;
    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
    }
    boolean isActive() {
        return runLock.isLocked();
    }
    void interruptIfIdle() {
        final ReentrantLock runLock = this.runLock;
        if (runLock.tryLock()) {
            try {
        if (thread != Thread.currentThread())
        thread.interrupt();
            } finally {
                runLock.unlock();
            }
        }
    }
    void interruptNow() {
        thread.interrupt();
    }
 
    private void runTask(Runnable task) {
        final ReentrantLock runLock = this.runLock;
        runLock.lock();
        try {
            if (runState < STOP &&
                Thread.interrupted() &&
                runState >= STOP)
            boolean ran = false;
            beforeExecute(thread, task);   //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据
            //自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等           
            try {
                task.run();
                ran = true;
                afterExecute(task, null);
                ++completedTasks;
            } catch (RuntimeException ex) {
                if (!ran)
                    afterExecute(task, ex);
                throw ex;
            }
        } finally {
            runLock.unlock();
        }
    }
 
    public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);   //当任务队列中没有任务时,进行清理工作       
        }
    }
}

Na verdade, ele implementa a interface Runnable, de modo que o Thread t = threadFactory.newThread (w); acima tem basicamente o mesmo efeito da seguinte frase:

Thread t = new Thread(w);

   É equivalente a passar uma tarefa Runnable e executá-la no thread t.

  Agora que Worker implementa a interface Runnable, o método central é naturalmente o método run ():

public void run() {
    try {
        Runnable task = firstTask;
        firstTask = null;
        while (task != null || (task = getTask()) != null) {
            runTask(task);
            task = null;
        }
    } finally {
        workerDone(this);
    }
}

   Pode-se ver a partir da implementação do método run que primeiro executa a tarefa firstTask passada através do construtor. Depois de chamar runTask () para executar a firstTask, ele usa continuamente getTask () no loop while para buscar novas tarefas para execução Então, onde obtê-lo? Naturalmente, ele é obtido da fila do cache de tarefas. GetTask é um método da classe ThreadPoolExecutor, não um método da classe Worker. O seguinte é a implementação do método getTask:

Runnable getTask() {
    for (;;) {
        try {
            int state = runState;
            if (state > SHUTDOWN)
                return null;
            Runnable r;
            if (state == SHUTDOWN)  // Help drain queue
                r = workQueue.poll();
            else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
                //则通过poll取任务,若等待一定的时间取不到任务,则返回null
                r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
            else
                r = workQueue.take();
            if (r != null)
                return r;
            if (workerCanExit()) {    //如果没取到任务,即r为null,则判断当前的worker是否可以退出
                if (runState >= SHUTDOWN) // Wake up others
                    interruptIdleWorkers();   //中断处于空闲状态的worker
                return null;
            }
            // Else retry
        } catch (InterruptedException ie) {
            // On interruption, re-check runState
        }
    }
}

   Em getTask, primeiro determine o estado atual do pool de threads, se runState for maior que SHUTDOWN (isto é, STOP ou TERMINATED), então retorne diretamente nulo.

  Se runState for SHUTDOWN ou RUNNING, a tarefa será buscada na fila do cache de tarefas.

  Se o número de threads no pool de threads atual for maior que o tamanho do pool principal corePoolSize ou o tempo de sobrevivência ocioso for permitido para os threads no pool principal, chame poll (time, timeUnit) para buscar a tarefa. irá esperar um certo tempo.Se a tarefa não puder ser buscada, retorna nulo.

  Em seguida, julgue se a tarefa r recuperada é nula. Se for nula, julgue se o trabalhador atual pode sair chamando o método workerCanExit (). Vejamos a implementação de workerCanExit ():

private boolean workerCanExit() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    boolean canExit;
    //如果runState大于等于STOP,或者任务缓存队列为空了
    //或者  允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1
    try {
        canExit = runState >= STOP ||
            workQueue.isEmpty() ||
            (allowCoreThreadTimeOut &&
             poolSize > Math.max(1, corePoolSize));
    } finally {
        mainLock.unlock();
    }
    return canExit;
}

   Ou seja, se o pool de threads estiver no estado STOP, ou a fila de tarefas estiver vazia, ou se o tempo de sobrevivência ocioso puder ser definido para os threads do pool principal e o número de threads for maior que 1, o trabalhador é permissão para sair. Se o trabalhador tiver permissão para sair, chame interruptIdleWorkers () para interromper o trabalhador inativo. Vamos dar uma olhada na implementação de interruptIdleWorkers ():

void interruptIdleWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)  //实际上调用的是worker的interruptIfIdle()方法
            w.interruptIfIdle();
    } finally {
        mainLock.unlock();
    }
}

   Pode-se ver pela implementação que ele realmente chama o método interruptIfIdle () do trabalhador, no método interruptIfIdle () do trabalhador:

void interruptIfIdle() {
    final ReentrantLock runLock = this.runLock;
    if (runLock.tryLock()) {    //注意这里,是调用tryLock()来获取锁的,因为如果当前worker正在执行任务,锁已经被获取了,是无法获取到锁的
                                //如果成功获取了锁,说明当前worker处于空闲状态
        try {
    if (thread != Thread.currentThread())  
    thread.interrupt();
        } finally {
            runLock.unlock();
        }
    }
}

    Existe um método de design muito inteligente. Se projetarmos um pool de threads, pode haver um thread de despacho de tarefas. Quando um thread está ocioso, uma tarefa é retirada da fila do cache de tarefas para o thread inativo para execução. Mas aqui, este método não é adotado, porque irá gerenciar adicionalmente o thread de despacho de tarefas, o que aumentará invisivelmente a dificuldade e a complexidade. Aqui, o thread que completou a tarefa é enviado diretamente para a fila de cache de tarefas para buscar a tarefa para execução.

   Vejamos a implementação do método addIfUnderMaximumPoolSize. A ideia de implementação deste método é muito semelhante à ideia de implementação do método addIfUnderCorePoolSize. A única diferença é que o método addIfUnderMaximumPoolSize é que o número de threads no pool de threads atinge o tamanho do pool principal e a adição de tarefas à fila de tarefas falha. Nestas circunstâncias:

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < maximumPoolSize && runState == RUNNING)
            t = addThread(firstTask);
    } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

   Veja se não é, de fato é basicamente o mesmo que a implementação do método addIfUnderCorePoolSize, exceto que poolSize <maximumPoolSize na condição de julgamento da instrução if é diferente.

  Neste ponto, a maioria dos meus amigos deve ter um conhecimento básico de todo o processo, desde quando a tarefa é enviada ao pool de threads até a sua execução. Aqui está um resumo:

  1) Primeiro, devemos ter clareza sobre o significado de corePoolSize e maximumPoolSize;

  2) Em segundo lugar, devemos saber para que serve o Trabalhador;

  3) Para saber a estratégia de processamento após a tarefa ser enviada ao pool de threads, aqui estão quatro pontos principais:

  • Se o número de threads no pool de threads atual for menor que corePoolSize, cada vez que uma tarefa vier, uma thread será criada para executar a tarefa;
  • Se o número de threads no pool de threads atual> = corePoolSize, toda vez que uma tarefa chegar, ela tentará adicioná-la à fila do cache de tarefas. Se a adição for bem-sucedida, a tarefa aguardará que a thread inativa a execute para execução; se a adição falhar (em geral, a fila do cache de tarefas está cheia), ele tentará criar um novo thread para executar esta tarefa;
  • Se o número de threads no pool de threads atual atingir o maximumPoolSize, a estratégia de rejeição da tarefa será adotada para processamento;
  • Se o número de threads no pool de threads for maior que corePoolSize, se o tempo ocioso de uma thread exceder keepAliveTime, a thread será encerrada até que o número de threads no pool de threads não seja maior que corePoolSize; se for permitido definir o tempo de sobrevivência para os threads no pool principal e, em seguida, o pool principal. Se o tempo de inatividade do thread exceder keepAliveTime, o thread também será encerrado.

3. Inicialização do thread no pool de threads

  Por padrão, após a criação do conjunto de encadeamentos, não há encadeamentos no conjunto de encadeamentos e o encadeamento será criado após o envio da tarefa.

  Na prática, se você precisar criar threads imediatamente após a criação do pool de threads, poderá fazer isso das duas maneiras a seguir:

  • prestartCoreThread (): inicializa um thread principal;
  • prestartAllCoreThreads (): inicializa todos os threads principais

  A seguir está a implementação desses dois métodos:

public boolean prestartCoreThread() {
    return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
 
public int prestartAllCoreThreads() {
    int n = 0;
    while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
        ++n;
    return n;
}

 

Acho que você gosta

Origin blog.csdn.net/sanmi8276/article/details/112779056
Recomendado
Clasificación