Em projetos de desenvolvimento, geralmente temos cenários em que precisamos iniciar tarefas assíncronas. Por exemplo, quando o usuário se registra com sucesso, alguns cupons precisam ser emitidos. Neste momento, para evitar que essas operações adicionais afetem o processo de registro do usuário, geralmente iniciamos um thread de forma assíncrona para executar a lógica de distribuição de cupons.
Normalmente, precisamos definir um pool de threads e iniciar uma tarefa de thread. Ele é simplificado no Springboot e um pool de threads de tarefas do tipo org.springframework.core.task.TaskExecutor é configurado automaticamente. Quando ativamos a anotação @EnableAsync, quando precisamos realizar tarefas assíncronas, adicione anotações @Async, este método Irá iniciar automaticamente um thread para ser executado.
Ao explicar como configurar o Springboot para iniciar tarefas assíncronas, vamos explicar brevemente o conhecimento básico do pool de threads Java.
Navegação neste artigo
Conhecimento básico de pool de threads
Use cenários de pool de threads
Normalmente alta simultaneidade, o tempo de execução da tarefa é curto, o tempo de criação do thread é T1, o tempo de execução da tarefa é T2 e o tempo de destruição do thread é T3. Quando o tempo de T1 + T2 é muito maior do que T2, o pool de threads pode ser usado. A função principal é reutilizar threads e reduzir a sobrecarga de tempo de criação e destruição de threads.
Conte convenientemente as informações de execução da tarefa, como o número de tarefas concluídas.
A interface de nível superior do pool de threads do Executor
Após a versão Jdk1.5, o pool de threads é fornecido para os desenvolvedores criarem facilmente suas próprias tarefas multi-threaded. A interface java.util.concurrent.Executor é a interface de nível superior do pool de encadeamentos.
A interface execute recebe uma instância de thread Runnable para executar uma tarefa.
public interface Executor {
void execute(Runnable command);
}
Explicação detalhada da interface ExecutorService
public interface ExecutorService extends Executor {
/**关闭线程池不会接收新的任务,内部正在跑的任务和队列里等待的任务,会执行完
*/
void shutdown();
/**
* 关闭线程池,不会接收新的任务
* 尝试将正在跑的任务interrupt中断
* 忽略队列里等待的任务
* 返回未执行的任务列表
*/
List<Runnable> shutdownNow();
/**
* 返回线程池是否被关闭
*/
boolean isShutdown();
/**
*线程池的任务线程是否被中断
*/
boolean isTerminated();
/**
*接收timeout和TimeUnit两个参数,用于设定超时时间及单位。
* 当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。和shutdown方法组合使用。
*/
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 提交任务会获取线程的执行结果
*/
<T> Future<T> submit(Callable<T> task);
/**
* 提交任务会获取线程的T类型的执行结果
*/
<T> Future<T> submit(Runnable task, T result);
/**
* 提交任务
*/
Future<?> submit(Runnable task);
/**
* 批量提交返回任务执行结果
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
/**
* 批量提交返回任务执行结果
*增加了超时时间控制,这里的超时时间是针对的所有tasks,而不是单个task的超时时间。
*如果超时,会取消没有执行完的所有任务,并抛出超时异常
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
/**
* 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
* 任务执行超时抛出异常
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Análise de parâmetro ThreadPoolExecutor
ThreadPoolExecutor é uma interface usada para processar tarefas assíncronas. Pode ser entendido como um pool de threads e uma fila de tarefas. As tarefas enviadas para o objeto ExecutorService serão colocadas na equipe de tarefas ou executadas diretamente por threads no pool de threads. ThreadPoolExecutor suporta ajustes Construa parâmetros para configurar diferentes estratégias de processamento de tarefas.
O código do construtor do pool de threads ThreadPoolExecutor é postado abaixo:
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 significado de cada parâmetro de entrada de ThreadPoolExecutor: A relação entre os parâmetros:
- Obtenha threads disponíveis do pool de threads para realizar tarefas. Se não houver threads disponíveis, use ThreadFactory para criar novas threads até que o número de threads atinja o limite corePoolSize
- Depois que o número de threads no pool de threads atingir corePoolSize, novas tarefas serão colocadas na fila até que a fila não possa mais acomodar mais tarefas
- Quando a fila não puder mais conter mais tarefas, um novo thread será criado até que o número de threads atinja o limite maxinumPoolSize
- Depois que o número de threads atingir o limite maxinumPoolSize, a nova tarefa será rejeitada para execução e o RejectedExecutionHandler será chamado para processamento
Estratégia de rejeição RejectedExecutionHandler
1. AbortPolicy: lança uma exceção diretamente, a política padrão;
2. CallerRunsPolicy: usa o thread do chamador para executar a tarefa;
3. DiscardOldestPolicy: descarta a primeira tarefa na fila de bloqueio e executa a tarefa atual;
4. DiscardPolicy: descarta diretamente tarefa;
Pontos-chave do uso de ThreadPoolExecutor
- Configure razoavelmente o tamanho do pool de threads: Se for uma tarefa com uso intensivo de CPU, o número de pools de threads pode ser definido como o número de núcleos de CPU + 1, se for uma tarefa intensiva de E / S, a CPU ficará mais ociosa e o número de pools de threads deve ser definido como o número de núcleos de CPU * 2
- Fila de tarefas: A fila de tarefas geralmente usa LinkedBlockingQueue para especificar o tamanho e é usada em conjunto com a estratégia de rejeição. O padrão é Integer.Max_VALUE, que está sujeito a estouro de memória.
- Exceção de tarefa: adicione try {} catch {} para capturar a exceção na atenção da tarefa no pool de threads, que é conveniente para solução de problemas
Conjunto de threads SpringBoot
org.springframework.core.task.TaskExecutor é a classe de interface do pool de thread assíncrono do Spring e sua essência é java.util.concurrent.Executor. O pool de threads usado por springboot2.3.x por padrão é ThreadPoolTaskExecutor, que pode ser facilmente ajustado por TaskExecutionProperties
SpringBoot define seu próprio pool de thread assíncrono
@EnableAsync
@Component
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(8);
// 设置最大线程数
executor.setMaxPoolSize(16);
// 设置队列容量
executor.setQueueCapacity(50);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("CodehomeAsyncTask-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
public Executor getAsyncExecutor() {
return taskExecutor();
}
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncExceptionHandler();
}
/**
* 自定义异常处理类
*/
class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
log.info("Exception message - " + throwable.getMessage());
log.info("Method name - " + method.getName());
for (Object param : objects) {
log.info("Parameter value - " + param);
}
}
}
}
Duas maneiras de usar
@Component
@Slf4j
public class UserServiceSyncTask {
//不带返回值
@Async
public void sendEmail(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread().getName());
}
//带返回值
@Async
public Future<String> echo(String msg){
try {
Thread.sleep(5000);
return new AsyncResult<String>(Thread.currentThread().getName()+"hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
}
Mil milhas começam com um único passo. Aqui está o décimo artigo da série de tutoriais do SpringBoot. Todos os códigos-fonte do projeto podem ser baixados no meu GitHub .