Desvantagens do novo Tópico
- Cada vez que um novo Thread cria um novo objeto, o desempenho é ruim
- Threads carecem de gerenciamento unificado e pode haver novos threads ilimitados competindo entre si e podem ocupar muitos recursos do sistema e causar travamentos ou OOM (sem memória). (A razão para este problema não é simplesmente um novo Thread, mas pode ser causado por um novo Thread contínuo devido a bugs do programa ou falhas de design)
- Falta de mais funções, como mais execução, execução regular, interrupção do thread.
Benefícios do pool de threads
- Reduza o consumo de recursos . Reutilize o encadeamento criado para reduzir a sobrecarga de criação e morte do encadeamento.
- Melhore a velocidade de resposta . Quando uma tarefa chega, ela pode ser executada imediatamente sem esperar que o thread seja criado.
- Melhore a capacidade de gerenciamento de threads . Threads são recursos escassos. Se forem criados ilimitadamente, não apenas consumirão recursos do sistema, mas também reduzirão a estabilidade do sistema. O pool de threads pode ser usado para alocação, ajuste e monitoramento uniformes. (Controle simultâneo, tempo / execução regular, etc.)
Diagrama de classe de pool de threads
Os executores na parte inferior são comumente usados e os usam para criar pools de threads e usar threads.
O framework Executor programa a execução e o controle de tarefas assíncronas de acordo com um conjunto de estratégias de execução, com o objetivo de fornecer um mecanismo para separar o envio de tarefas da operação de tarefas .
- Executor: uma interface simples para executar novas tarefas
- ExecutorService: estende o Executor e adiciona métodos para gerenciar o ciclo de vida do executor e o ciclo de vida das tarefas
- ScheduleExcutorService: Extended ExecutorService para suportar tarefas futuras e agendadas
O princípio de realização do pool de threads
A relação entre corePoolSize, maximumPoolSize e workQueue
- corePoolSize (número de threads principais, número de threads básicas): Ao enviar uma tarefa para o pool de threads, o pool de threads criará uma thread para realizar a tarefa (mesmo se houver threads básicas inativos. Quando o número de tarefas for maior que corePoolSize, ele não será criado). Se o método prestartAllCoreThreads () do conjunto de encadeamentos for chamado, o conjunto de encadeamentos criará e iniciará todos os encadeamentos básicos com antecedência.
- runnableTaskQueue (fila de tarefas): fila de bloqueio workQueue, armazenamento de tarefas esperando para serem executadas (quando o número de threads em execução é maior que corePoolSize e menor que maximumPoolSize)
- maximumPoolSize: O número máximo de threads. Se a fila estiver cheia e o número de encadeamentos criados for menor que o número máximo de encadeamentos, o pool de encadeamentos criará novos encadeamentos para executar tarefas. (Se uma fila de tarefas ilimitada for usada, o número máximo de threads que podem ser criados é corePoolSize, então maximumPoolSize não funcionará)
Taxa de transferência: SynchronousQueue é maior que LinkedBlockingQueue e ArrayBlockingQueue
4 situações em que ThreadPoolExecutor executa execute ()
1) Se o encadeamento atualmente em execução for menor que corePoolSize, crie um novo encadeamento para realizar a tarefa (você precisa adquirir um bloqueio global ).
2) Se o thread em execução for igual ou maior que corePoolSize, a tarefa será adicionada ao BlockingQueue.
3) Se a tarefa não puder ser adicionada ao BlockingQueue (a fila está cheia), um novo thread é criado para processar a tarefa (um bloqueio global precisa ser adquirido ).
4) Se a criação de um novo encadeamento fizer com que o encadeamento atualmente em execução exceda o máximoPoolSize, a tarefa será rejeitada e o método RejectedExecutionHandler.rejectedExecution () será chamado.
设计思路
, É para evitar a aquisição de um bloqueio global (um sério gargalo de escalabilidade ) tanto quanto possível ao executar o método execute () . Depois que ThreadPoolExecutor termina de aquecer (o número de threads em execução é maior ou igual a corePoolSize), quase todas as chamadas de método execute () são executadas na etapa 2, e a etapa 2 não precisa adquirir um bloqueio global.
jdk版本不同会有区别
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//只有当前线程池中线程数poolSize <corePoolSize 时,则创建线程并执行当前任务
//当poolSize >=corePoolSize 或线程创建失败,则将当前任务放到工作队列中。
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
//如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量,则创建一个线程执行任务。
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//抛出RejectedExecutionException异常
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
Thread de trabalho: quando o pool de threads cria um thread, ele encapsula o thread em um Worker de thread de trabalho. Depois que o trabalhador termina de executar a tarefa, ele também obtém as tarefas na fila de trabalho para execução em um loop ( não será GC ) O método run () da classe Worker:
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
Criação de pool de threads
Podemos criar um pool de threads por meio de ThreadPoolExecutor.
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit ,
BlockingQueue<Runnable> workQueue ,ThreadFactory threadFactory, RejectedExecutionHandler handler);
ThreadFactory: Thread factory, definido para criar threads, você pode definir um nome para cada thread criado através da thread factory. O ThreadFactoryBuilder fornecido pela estrutura de software livre guava pode definir rapidamente nomes significativos para threads no pool de threads. O código é o seguinte.
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
RejectedExecutionHandler (estratégia de saturação): Quando a fila e o pool de threads estão cheios, indicando que o pool de threads está em um estado saturado, uma estratégia deve ser adotada para lidar com as novas tarefas enviadas.
.AbortPolicy:直接抛出异常。
·CallerRunsPolicy:使用 【调用者 dubbo生产者主线程】所在线程(execute 方法的调用线程) 来运行任务(可能会影响主线程)。
·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
·DiscardPolicy:不处理,丢弃掉。
Você também pode implementar uma estratégia personalizada para a interface RejectedExecutionHandler de acordo com as necessidades do cenário do aplicativo. Como registro ou armazenamento persistente não pode lidar com tarefas.
keepAliveTime (tempo de retenção da atividade do thread): O tempo que os threads de trabalho do pool de threads permanecem ativos após ficarem ociosos. Portanto, se houver muitas tarefas e o tempo de execução de cada tarefa for relativamente curto, o tempo pode ser ajustado para aumentar a utilização de threads.
TimeUnit: a unidade de tempo de keepAliveTime
Explicação detalhada de três métodos estáticos de executores de classe de fábrica
Envie tarefas para o pool de threads
O método execute () é usado para enviar tarefas que não requerem um valor de retorno , portanto, é impossível determinar se a tarefa foi executada com sucesso pelo pool de threads.
O código a seguir mostra que a tarefa inserida pelo método execute () é uma instância da classe Runnable.
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
O método submit () é usado para enviar tarefas que requerem um valor de retorno . O pool de threads retornará um objeto de tipo futuro . Por meio desse objeto futuro, pode-se avaliar se a tarefa foi executada com sucesso e o valor de retorno pode ser obtido por meio do método get () do futuro. O método get () bloqueará o encadeamento atual até que a tarefa seja concluída e use get O método (tempo limite longo, unidade TimeUnit) bloqueará o encadeamento atual por um período de tempo e retornará imediatamente. Nesse momento, a tarefa pode não ser concluída.
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();//获取返回值
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理无法执行任务异常
} finally {
// 关闭线程池
executor.shutdown();
}
Fechar pool de discussão
O método shutdown ou shutdownNow fecha o pool de threads.
Princípio : Percorra os threads de trabalho no pool de threads e, a seguir, chame o método de interrupção do thread, um por um, para interromper o thread, de forma que a tarefa que não pode responder à interrupção nunca seja encerrada.
a diferença
- shutdownNow () define o estado do pool de threads como STOP, tenta parar todas as threads que estão executando ou suspendendo tarefas e retorna a lista de tarefas esperando para serem executadas
- shutdown () define o estado do pool de threads para o estado SHUTDOWN e interrompe todos os threads que não estão executando tarefas.
Outro status
- SHUTDOWN não pode processar novas tarefas, mas pode continuar a processar tarefas na fila de bloqueio
- parar: não pode receber novas tarefas, nem processar tarefas na fila
- arrumação: Se todas as tarefas foram encerradas, o número de threads efetivos é 0
- terminado: estado final
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
/**
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks
* that were awaiting execution. These tasks are drained (removed)
* from the task queue upon return from this method.
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
Configure razoavelmente o pool de threads
As características da tarefa devem ser analisadas primeiro, o que pode ser analisado a partir das seguintes perspectivas.
- A natureza da tarefa: tarefas intensivas de CPU, tarefas intensivas de E / S e tarefas mistas.
- A prioridade da tarefa: alta, média e baixa.
- Tempo de execução da tarefa: longo, médio e curto.
- Dependência de tarefa: se depende de outros recursos do sistema, como conexões de banco de dados.
Tarefas de natureza diferente podem ser processadas separadamente por pools de threads de tamanhos diferentes.
- Tarefas com uso intensivo de CPU devem ser configuradas com o mínimo de threads possível, como configurar um pool de threads de Ncpu + 1 threads.
- Como os threads de tarefa com IO intensivo nem sempre estão executando tarefas, você deve configurar o máximo de threads possível, como 2 * Ncpu.
- Se uma tarefa mista puder ser dividida, divida-a em uma tarefa com uso intensivo de CPU e uma tarefa com uso intensivo de E / S. Contanto que a diferença de tempo entre a execução das duas tarefas não seja muito grande, a taxa de transferência após a decomposição será maior do que Taxa de transferência de execução serial. Se o tempo de execução dessas duas tarefas for muito diferente, não há necessidade de decompor.
- Confie na tarefa do pool de conexão do banco de dados, porque o thread precisa esperar que o banco de dados retorne o resultado após o envio do SQL. Quanto maior o tempo de espera, maior o tempo ocioso da CPU. Então, o número de threads deve ser definido maior , para aproveitar melhor a CPU.
O número de CPUs do dispositivo atual pode ser obtido através do método Runtime.getRuntime (). AvailableProcessors ().
Tarefas com prioridades diferentes podem ser processadas usando a fila de prioridade PriorityBlockingQueue.
Nota : Se sempre houver tarefas de alta prioridade enviadas para a fila, as tarefas de baixa prioridade podem nunca ser executadas.
É recomendável usar o
máximo maximumPoolSize da fila limitada , o que pode aumentar a estabilidade e a capacidade de aviso antecipado do sistema e pode reduzir o consumo de recursos.No entanto, esse método torna o agendamento de threads mais difícil para o pool de threads.
Eu quero fazer [taxa de transferência do pool de threads] [tarefas de processamento] atingir um intervalo razoável, de modo que [o agendamento da thread é relativamente simples] [reduza o consumo de recursos o máximo possível] limite razoavelmente [thread pool] e [capacidade da fila]
Habilidades de atribuição
- Reduza o consumo de recursos [uso de CPU, consumo de recursos do sistema operacional, sobrecarga de troca de contexto] defina uma [capacidade de fila maior] [capacidade de pool de encadeamento menor] para reduzir a taxa de transferência do pool de encadeamento
- As tarefas enviadas frequentemente bloqueiam, você pode ajustar o maximumPoolSize
- A capacidade da fila é pequena e o tamanho do pool de threads precisa ser definido maior, para que a taxa de uso da CPU seja relativamente maior
- Se a capacidade do pool de encadeamentos for definida muito grande e o número de tarefas for muito aumentado, a quantidade de simultaneidade aumentará e o agendamento entre os encadeamentos precisará ser considerado. Isso pode reduzir o rendimento das tarefas de processamento.
Monitoramento de pool de threads
· TaskCount: O número de tarefas que o pool de threads precisa executar.
· CompletedTaskCount: O número de tarefas concluídas pelo pool de threads durante o processo de execução, que é menor ou igual a taskCount.
· LargestPoolSize: O número máximo de threads já criado no pool de threads. Por meio desses dados, você pode saber se o pool de threads está cheio. Se o valor for igual ao tamanho máximo do conjunto de encadeamentos, isso significa que o conjunto de encadeamentos está cheio.
· GetPoolSize: o número de threads no pool de threads. Se o thread pool não for destruído, os threads no pool não serão destruídos automaticamente, portanto, o tamanho só aumentará.
· GetActiveCount: Obtenha o número de threads ativos.
Monitore expandindo o pool de threads. Você pode personalizar o pool de threads herdando o pool de threads, reescrever os métodos beforeExecute, afterExecute e encerrados do pool de threads ou executar algum código para monitorar antes, depois e antes da tarefa ser executada e antes que o pool de threads seja fechado . Por exemplo, monitore o tempo médio de execução, o tempo máximo de execução e o tempo mínimo de execução das tarefas.
protected void beforeExecute(Thread t, Runnable r) {
这几个方法在线程池里是空方法。
}