Pool de threads:
Para evitar a criação de threads repetidamente, a aparência dos conjuntos de threads permite que os threads sejam reutilizados. Quando uma tarefa é enviada, um encadeamento é retirado do conjunto de encadeamentos.Quando a tarefa é concluída, o encadeamento não é fechado diretamente, mas o encadeamento é retornado ao conjunto de encadeamentos para uso por outras tarefas. Dessa maneira, a sobrecarga desnecessária de desempenho causada pela criação frequente de encadeamentos pode ser evitada.
Usar pool de threads:
Após a versão java1.5, recomenda-se o uso de conjuntos de encadeamentos para usar os três métodos estáticos que foram empacotados no pacote java.util.concurrent:
- Crie um conjunto de encadeamentos com um tamanho de encadeamento especificado
Executor executor = Executors.newFixedThreadPool (int nThreads);
- Criar um pool de threads com 1 thread
Executor executor = Executors.newSingleThreadExecutor();
- Não especifique o tamanho, crie um pool de threads que possa adicionar segmentos dinamicamente
Executor executor = Executors.newCachedThreadPool();
O princípio do pool de threads:
De fato, os três métodos acima são, na verdade, os métodos de construção da classe ThreadPoolExecutor chamados, mas os parâmetros são inconsistentes. Vamos examinar os métodos de construção da classe ThreadPoolExecutor:
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;
}
Use uma tabela para explicar o significado dos parâmetros requeridos por este método:
Nome do parâmetro | Explique |
---|---|
int corePoolSize | Tamanho do pool principal |
int maximumPoolSize | O tamanho do maior conjunto de encadeamentos |
long keepAliveTime | O tempo máximo de sobrevivência de encadeamentos inativos no conjunto de encadeamentos que excede corePoolSize |
Unidade TimeUnit | A unidade de tempo de keepAliveTime é uma variável enumerada |
BlockingQueue < Executável > | Bloqueando fila de tarefas |
ThreadFactory threadFactory | Fábrica de linhas |
Manipulador RejectedExecutionHandler | Estratégia de rejeição do conjunto de encadeamentos |
Fluxo de trabalho do conjunto de encadeamentos
- Se o número de encadeamentos no conjunto de encadeamentos atual for menor que corePoolSize, toda vez que uma tarefa for enviada, um encadeamento será criado para executar a tarefa;
- Se o número de conjuntos de encadeamentos for maior que corePoolSize, sempre que um encadeamento for enviado, ele tentará enviá-lo para a fila de cache de tarefas.Se a adição for bem-sucedida, a tarefa executará a tarefa na fila de cache quando o encadeamento estiver inativo; se a adição falhar (geralmente Crie uma fila de cache limitada) e, em seguida, outro encadeamento será criado para executar esta tarefa (nesse caso, a soma do número máximo de encadeamentos MaximumPoolSize e o comprimento da fila de cache é menor que o número total de encadeamentos que você precisa criar).
- Se a soma do número máximo de threads MaximumPoolSize e o comprimento da fila de cache for maior que o número total de threads que você precisa criar, uma estratégia de rejeição de tarefa será adotada para processamento;
- Se o número de encadeamentos no conjunto de encadeamentos for maior que corePoolSize, se o tempo ocioso de um encadeamento exceder keepAliveTime, o encadeamento será encerrado até que o número de encadeamentos no conjunto de encadeamentos não seja maior que corePoolSize; se for permitido definir o tempo de sobrevivência para os encadeamentos no conjunto principal, o conjunto principal O tempo ocioso do encadeamento em mais de keepAliveTime, o encadeamento também será encerrado;
Código para demonstrar esse fluxo de trabalho
package RejectThread;
//需要执行的任务线程
public class SendNoticeTask implements Runnable {
private int count;
public void setCount(int count) {
this.count = count;
}
@Override
public void run() {
System.out.println (Thread.currentThread ().getName () + "号工人 开始进行" + count + "号工作任务");
try {
Thread.currentThread ().sleep (5000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println (Thread.currentThread ().getName ()+"号工人 执行完成");
}
}
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形一
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> ());
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 3; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
Aqui, definimos o tamanho do pool de núcleos como 3, o número máximo de threads é 10 e a fila do cache de tarefas não é definida como um tamanho.Quando o número de threads que você enviar for menor ou igual a 3, o resultado será basicamente o mesmo:
se redefinirmos o envio O número de tarefas, definimos o número de threads a serem executados como 6, a fila de cache não define o número:
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> ());
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 6; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
Veja os resultados: É possível observar que as três primeiras tarefas 0, 1 e 2 são executadas diretamente, e as três tarefas 3, 4, 5 são colocadas na fila de cache e aguarde até que as três primeiras threads estejam livres. Ele é retirado apenas da fila de cache para execução. No momento, não importa quantos encadeamentos enviados, a estratégia de rejeição não está implementada, pois pode ser enviada para a fila de cache.
Vamos analisar o caso de adicionar falha, definimos o tamanho da fila LinkedBlockingQueue como 1 Ou seja, apenas um segmento pode ser inserido e observe o resultado da execução:
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));//队列数量为1
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 6; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
Veja os resultados da execução: pode-se ver que as tarefas de trabalho 0, 1, 2, 4 e 5 criam threads diretamente e as executam, enquanto a tarefa de trabalho 3 é armazenada na fila de cache e aguarda a disponibilidade do thread. ele é executado.
na verdade, este processo é, antes de 0,1,2 três tarefas são criadas diretamente segmento de execução, porque o tamanho do corePoolSize é 3, e em seguida, veio a tarefa de 3,4,5, de jeito nenhum, para o cache Ele é adicionado à fila, mas o tamanho da fila de cache é 1, para que apenas um possa ser adicionado, ou seja, a tarefa 3, as tarefas 4 e 5 criam diretamente um novo encadeamento para execução e aguarde o encadeamento ficar ocioso quando estiver ocioso. Retire a tarefa 3 da fila para continuar a execução.
O caso três é explicado na estratégia de rejeição.
Estratégia de rejeição do conjunto de encadeamentos
Este artigo demonstra principalmente a estratégia de rejeição do conjunto de encadeamentos, existem 4 tipos no total:
Manipulador da estratégia de rejeição | Explique |
---|---|
AbortPolicy | Descartar a tarefa e lançar RejectedExecutionException |
DescartarPolítica | Descartar a tarefa, mas não lançar uma exceção |
DiscardOldestPolicy | Descarte a primeira tarefa na fila e tente executar a tarefa novamente (repita esse processo) |
CallerRunsPolicy | A tarefa é tratada pelo encadeamento de chamada (o mais comum é o encadeamento principal) |
Nos negócios acima, quando a estratégia de rejeição é usada, configuramos o corePoolSize como 3, o maximumPoolSize é 10 e o tamanho da fila de cache é 1. Desta vez, realizamos 15 tarefas, desta vez usaremos a estratégia de rejeição, o código é o seguinte :
- AbortPolicy: lançar política de rejeição de exceção:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 15; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
Os resultados são os seguintes: Observa-
se que as tarefas 0, 1, 2 são executadas diretamente primeiro, a tarefa 3 entra na fila de cache e novos encadeamentos são criados para execução em 4, 5, 6, 7, 8, 9, 10 e o máximo foi atingido neste momento O número de criação do encadeamento é 10, ele não pode ser criado e a estratégia de rejeição é executada. As tarefas subsequentes 11, 12, 13, 14 são descartadas diretamente e uma exceção é lançada.
- Descartar tarefas sem lançar políticas anormais DiscardPolicy
public DiscardOldestPolicy() { }
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardPolicy ());//不抛异常的拒绝策略
for (int i = 0; i < 15; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
O resultado da execução é relativamente simples, sem análise:
- O encadeamento de chamada lida com a tarefa (o mais comum é o encadeamento principal) A estratégia CallerRunsPolicy
alterou a saída do encadeamento que executava a tarefa para facilitar a visualização do efeito:
package RejectThread;
public class SendNoticeTask implements Runnable {
private int count;
public void setCount(int count) {
this.count = count;
}
@Override
public void run() {
System.out.println (Thread.currentThread ().getName () + "号工人 开始进行" + count + "号工作任务");
try {
Thread.currentThread ().sleep (5000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println (Thread.currentThread ().getName ()+"号工人 执行完成" + count + "号工作任务~~~~~~");
}
}
Use a estratégia de CallerRunsPolicy:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
Observe aqui: aqui chame diretamente o run () que precisa executar o encadeamento, o que equivale a chamar diretamente o método (você pode entender a diferença entre chamar run () e start () para iniciar o encadeamento), porque ele é o principal Dentro do encadeamento, ele representa a estratégia que o executor executa. CallerRun significa isso em inglês.
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.CallerRunsPolicy ());
for (int i = 0; i < 15; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
Veja o resultado da execução: o
resultado da execução é especial.Primeiro, ele não lança uma exceção.Segundo, o encadeamento principal (principal) é adicionado.A figura é o primeiro resultado pop-up.Os resultados da análise podem ser entendidos da seguinte forma: Esta estratégia é equivalente ao encadeamento principal. O conjunto de encadeamentos, ou seja, a capacidade máxima do encadeamento agora é 11, a tarefa 3 ingressou na fila de cache que definimos, ou seja, quando a tarefa 11 foi enviada, a estratégia de rejeição foi implementada e a tarefa foi atribuída ao encadeamento principal. O thread principal também leva tempo para executar a tarefa.Todos os loops para envio de tarefas são bloqueados aqui.Espere até que o thread principal conclua a execução antes de continuar a enviar a próxima tarefa.Neste momento, os threads no pool de threads já concluíram parcialmente a execução, para que possam continuar executando. Tarefas posteriores.
- Descarte a tarefa no início da fila e tente executar a tarefa novamente (repita esse processo) DiscardOldestPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
System.out.println("主线程结开始");
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (2));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardOldestPolicy ());
for (int i = 0; i < 13; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
System.out.println("主线程结束");
Os resultados da execução são os seguintes: Os
resultados da análise podem ser vistos, o processo anterior é consistente, as tarefas 0, 1, 2 são criadas diretamente, 3, 4 são adicionadas à fila de cache, 5 a 11 threads recém-criados são executados e você envia 12 Tarefas, o fluxo de processamento do sistema é que você envie um novo encadeamento, exclua primeiro um encadeamento na fila de cache, chame o
método poolExecutor.execute () para executar novamente, até que todas as tarefas estejam atualizadas, para que a fila de cache seja descartada A primeira tarefa é a tarefa 3, para que as tarefas 4 e 12 sejam executadas posteriormente.
Sumário
A estratégia de rejeição do conjunto de encadeamentos é a dos quatro acima. Se o acima ainda não puder atender aos seus requisitos, você precisará personalizar a estratégia de rejeição. A prática é escrever uma classe para implementar a interface RejectedExecutionHandler e personalizar sua lógica de código:
package RejectThread;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class MyRejectPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (!executor.isShutdown()) {
executor.getQueue().poll ();
System.out.println("自定义的拒绝策略");
executor.submit (r);
//r.run ();
}
}
}
Realizar teste de código:
package RejectThread;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
System.out.println("主线程结开始");
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<> (2));
poolExecutor.setRejectedExecutionHandler (new MyRejectPolicy());
for (int i = 0; i < 14; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
executor.submit (r);
}
System.out.println("主线程结束");
Resultados de saída: a
análise anterior deve ser muito simples, principalmente ao enviar a tarefa nº 12, ela primeiro imprime nossa saída e, em seguida, usa o pool de threads para executar a tarefa. Nesse momento, é necessário aguardar.