índice
Introdução básica ao pool de threads
Status do pool de threads e métodos básicos
O processo de execução do pool de threads
Introdução básica ao pool de threads
A taxa de aparecimento do pool de threads no projeto ainda é relativamente alta. Se o pool de threads for introduzido em uma linguagem mais vernácula, é uma coleção de threads + fila de tarefas. Quando uma solicitação de tarefa chega ao pool de threads, ela é colocada em a fila de tarefas e, em seguida, Um cenário simples onde os threads pegam tarefas da fila em um loop infinito. Então, por que precisamos de um pool de threads?
- Evite a criação e destruição frequente de threads e reduza o consumo de recursos
- O pool de threads pode gerenciar melhor os recursos de thread, como alocação, ajuste e monitoramento
- Melhore a velocidade de resposta, quando a tarefa chegar, ela pode responder sem esperar que o thread seja criado
Parâmetros do pool de threads
Na verdade, o jdk nos fornece vários conjuntos de encadeamentos integrados, mas não é recomendado usá-los. É melhor criá-los manualmente. Os parâmetros do método para criar conjuntos de encadeamentos são os seguintes:
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;
}
O seguinte descreve esses parâmetros em um pequeno detalhe e, em seguida, analisa o uso de cada parâmetro em combinação com o código-fonte do processo de execução do pool de threads
int corePoolSize:线程池中的核心线程数量,即使没有任务,这些线程也不会销毁。
int maximumPoolSize:线程池中的最大线程数量,即线程池所支持创建的最大数量线程,即使任务超级多,也只会有 maximumPoolSize 数量的线程在运行。
long keepAliveTime:非核心线程的存活时间,当任务数量超过核心线程数量时,只要 corePoolSize < maximumPoolSize,线程池便会创建对应的线程数去执行任务,当线程池中存活的线程数量大于核心线程时,如果等了 keepAliveTime 时间仍然没有任务进来,则线程池会回收这些线程。
TimeUnit unit:非核心线程存活时间的具体单位,即等待多少毫秒、秒等。
BlockingQueue<Runnable> workQueue:存储线程任务所用的队列,提交的任务将会被放到该队列中。
ThreadFactory threadFactory:线程工厂,主要用来创建线程的时候给线程取名用,默认是pool-1-thread-3
RejectedExecutionHandler handler:线程拒绝策略,当存储任务所用的队列都被填满时,新来的任务此时无处存放,那么需要提供一种策略去解决这种情况。
Status do pool de threads e métodos básicos
Esta classe define o estado básico e os métodos básicos de alguns pools de threads. Você precisa saber sobre isso antes de ler o código-fonte:
/*
* 该对象高3位维护线程池运行状态,低29位维护当前线程池数量
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/*
* 该值等于29,用于左移
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/*
* 线程池支持的最大线程数量,即29位所能表示的最大数值,2^29 - 1
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/*
* 即高3位为111,低29位为0,该状态的线程池会接收新任务,并且处理阻塞队列中正在等待的任务
*/
private static final int RUNNING = -1 << COUNT_BITS;
/*
* 即高3位为000,不接受新任务,但仍会处理阻塞队列中正在等待的任务
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/*
* 高3位为001,不接受新任务也不处理阻塞队列中的任务
*/
private static final int STOP = 1 << COUNT_BITS;
/*
* 高3位为010,所有任务都被终止了,workerCount为0,为此状态时还将调用terminated()方法
*/
private static final int TIDYING = 2 << COUNT_BITS;
/*
* 高3位为011,terminated()方法调用完成后变成此状态
*/
private static final int TERMINATED = 3 << COUNT_BITS;
Esses estados são representados pelo tipo int, e o relacionamento de tamanho é RUNNING <SHUTDOWN <STOP <TIDYING <TERMINATED, e essa sequência basicamente segue o processo do pool de threads da execução ao encerramento. Os três métodos a seguir são comumente usados nesta classe para obter o status do pool de threads e o número de threads:
/*
* c & 高3位为1,低29位为0的~CAPACITY,用于获取高3位保存的线程池状态
*/
private static int runStateOf(int c) { return c & ~CAPACITY; }
/*
* c & 高3位为0,低29位为1的CAPACITY,用于获取低29位的线程数量
*/
private static int workerCountOf(int c) { return c & CAPACITY; }
/*
* 参数rs表示runState,参数wc表示workerCount,即根据runState和workerCount打包合并成ctl
*/
private static int ctlOf(int rs, int wc) { return rs | wc; }
O processo de execução do pool de threads
A lógica de execução específica está principalmente no método execute () de ThreadPoolExecutor . O código-fonte é o seguinte:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// ctl对象功能很强大,其高3位代表线程池的状态,低29位代表线程池中的线程数量
int c = ctl.get();
// 如果当前线程池中线程数量小于核心线程数,则新建一个线程并将当前任务直接赋予该线程执行
if (workerCountOf(c) < corePoolSize) {
// 如果新建线程成功则直接返回
if (addWorker(command, true))
return;
// 到这一步说明新建失败,可能是线程池意外关闭或者是由于并发的原因导致当前线程数大于等于核心线程数了,重新获取ctl对象
c = ctl.get();
}
// 如果当前线程处于运行态并且任务入队列成功,则继续执行下面的逻辑
if (isRunning(c) && workQueue.offer(command)) {
// 这里需要再次确认线程池是否仍处于运行态
int recheck = ctl.get();
// 如果非运行态则需要删除队列中的任务,然后拒绝该任务
if (! isRunning(recheck) && remove(command))
reject(command);
// 确保线程池中仍有运行的线程,如果都未存活则新建一个线程且不指定任务参数,让该线程自行去队列中获取任务执行
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果处于非运行态或者入队列不成功(队列满了),尝试扩容线程池线程数量至maxPoolSize,若扩容失败,则拒绝该任务
else if (!addWorker(command, false))
reject(command);
}
Para resumir brevemente, é principalmente dividido em três etapas:
- Primeiro julgue se o número de threads em execução é menor que o número de threads principais, em caso afirmativo, crie uma nova thread para processar a tarefa e retorne diretamente se o processamento for bem-sucedido, caso contrário, continue a julgar
- Determine se o pool de threads atual está em execução e se a tarefa foi enfileirada com êxito e, em caso afirmativo, verifique novamente para garantir que ainda haja threads em execução no pool
- Se a segunda etapa na fila falhar, o pool de threads tentará se expandir para o número máximo de threads, se falhar, a tarefa será rejeitada
Uma vez que as etapas 1 e 3 acima precisam ser bloqueadas ao adicionar threads, isso afetará o desempenho, então a maioria das operações do pool de threads são executadas na segunda etapa (contando com o número de threads principais para suportar), a menos que a fila de bloqueio não cabe mais. Pode-se ver no código acima que mesmo se o número de threads principais for definido, o número de threads corePoolSize não é criado com antecedência, mas o bloqueio é adicionado a cada vez. Portanto, para considerações de desempenho, você pode chamar prestartAllCoreThreads () Método para iniciar o número corePoolSize de threads com antecedência.
Vamos dar uma olhada no método addWorker () para criar um novo thread
private boolean addWorker(Runnable firstTask, boolean core) {
// 首先是一个外层死循环
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 检查当前线程池是否处于非运行态,同时确保队列中任务数不为空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 内存死循环修改运行的线程数量
for (;;) {
int wc = workerCountOf(c);
// core参数确保不会超过线程池设定的值
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 采用CAS算法将线程数+1,如果成功则直接跳出外循环,失败主要是因为并发修改导致,那么则再次内循环判断
if (compareAndIncrementWorkerCount(c))
break retry;
// 确保线程池运行状态没变,若发生改变,则从外循环开始判断
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 新建Worker内部类时主要干了两件事,一个是设置AQS同步锁标识为-1,另一个是调用线程工厂创建线程并赋值给Worker的成员变量
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 在往workers这个线程集合增加线程时需要进行加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 此处需要进行二次检查,防止线程池被关闭等异常情况
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 如果当前线程已经启动,处于活跃态则抛异常
if (t.isAlive())
throw new IllegalThreadStateException();
// workers是一个HashSet集合
workers.add(w);
int s = workers.size();
// 设置最大池大小,同时标识任务线程增加成功,即 workerAdded 设为true
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果任务线程成功增加,则在此处启动线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Parece ser muito longo, mas na verdade está fazendo alguns julgamentos básicos para evitar situações anormais. Um breve resumo:
- Primeiro determine se ele está no estado de execução e se o número de threads em execução é menor que o limite superior, em caso afirmativo, o número de threads em execução é adicionado primeiro pelo algoritmo CAS
- Depois que a operação CAS for bem-sucedida, crie um novo thread de trabalho e adicione-o à coleção de threads travando e, em seguida, inicie o thread
Em seguida, olhe para a classe interna Worker . Esta classe envolve o thread de tarefa, principalmente para controlar a interrupção do thread, ou seja, quando o pool de thread é fechado, a tarefa do thread correspondente precisa ser interrompida. A interrupção mencionada aqui está esperando para obter o tarefa do workQueue getTask () Só pode ser interrompida, ou seja, a interrupção é permitida depois que a thread realmente começa a ser executada, então o estado de bloqueio é um valor negativo (-1) durante a inicialização.
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** 由线程工厂所创建的线程对象 */
final Thread thread;
/** 业务任务对象 */
Runnable firstTask;
/** 当前线程所执行任务的计数 */
volatile long completedTasks;
/**
* 初始化方法,主要是设置AQS的同步状态private volatile int state,是一个计数器,大于0代表锁已经被获取,设为-1后即禁止中断
*/
Worker(Runnable firstTask) {
setState(-1); // 将lock标识设为-1,
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
/**
* 下面的都是与锁相关的方法,state为0代表锁未被获取,1代表锁已经被获取
*/
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
// 控制中断主要就是体现在中断前会判断 getState() >= 0
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
A própria classe Worker implementa Runnable e herda AbstractQueuedSynchronizer, portanto, não é apenas uma tarefa executável, mas também o efeito de um bloqueio. Observe que o bloqueio é um bloqueio não reentrante simples
Por fim, observe o método runWorker () que executa principalmente tarefas de negócios no pool de threads:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 将state置为0,允许线程中断
w.unlock();
boolean completedAbruptly = true;
try {
// task为上层透传进来的指定业务线程,若为空则循环通过getTask()获取任务执行
while (task != null || (task = getTask()) != null) {
// 这里的加锁不是为了防止并发,而是为了在shutdown()时不终止正在运行的任务
w.lock();
// 双重检查防止线程池状态不大于stop且未被设为中断标识
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++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 处理业务线程抛异常的情况
processWorkerExit(w, completedAbruptly);
}
}
Pool de threads integrado
O JDK incorporou quatro pools de threads para nós, todos criados por meio da classe de fábrica Executors. Não é recomendado criar pools de threads dessa maneira. A seguir, o motivo.
newFixedThreadPool
Este é um pool de threads de comprimento fixo
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Como o nome indica, um pool de threads de comprimento fixo é criado. O número de threads principais no pool de threads é igual ao número máximo de threads. Portanto, o parâmetro de tempo de sobrevivência de thread não principal não tem sentido. O maior problema é que a fila de bloqueio selecionada é ilimitada. Infinitamente, ele continuará adicionando e adicionando, eventualmente estourando a memória. Geralmente é usado para servidores com cargas mais pesadas e precisa limitar o número especificado de threads.
newSingleThreadExecutor
Este é um pool de threads de thread único
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Olhando para os parâmetros, você também pode descobrir que apenas um thread foi inicializado e há apenas um thread funcionando no pool do início ao fim. Parece realmente lamentável. O único problema é que a fila de bloqueio é ilimitada, que pode estourar memória. Geralmente é usado para requisitos estritos. Cenários que controlam a ordem de execução da tarefa.
newCachedThreadPool
Este é um pool de threads que pode ser armazenado em cache
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
O número de encadeamentos principais é 0 e o número máximo de encadeamentos tende a ser infinito, ou seja, todos os encadeamentos não principais são usados para processar tarefas e o tempo de sobrevivência de cada encadeamento é 60s. Observe que uma fila síncrona é usada aqui, ou seja, apenas as tarefas serão entregues e não serão salvas. Tarefa, uma vez que haja uma tarefa, verifique se há um thread inativo, caso contrário, crie um novo thread. Quando a taxa de solicitação de tarefa for maior que o taxa de processamento de thread, o número de threads aumentará e, eventualmente, a memória irá estourar. Geralmente usado para simultaneidade Execute um grande número de pequenas tarefas de curto prazo, porque o thread que está ocioso por 60 segundos será reciclado, portanto, o pool de threads que fica ocioso por muito tempo não ocupará nenhum recurso.
newScheduledThreadPool
Este também é um pool de threads de comprimento fixo, mas suporta tempo e execução periódica de tarefas
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
Pode-se ver que o número máximo de threads ainda é infinito, então haverá um problema de burst de memória até certo ponto. No uso da fila, a fila de bloqueio atrasado DelayedWorkQueue é selecionada, e a execução da tarefa específica também é mais complicado.