一.前言
本文主要介绍@Async在日常生产活动中的使用和注意点。
二.实现原理
三.实现代码及过程
1.yaml线程池配置参数
spring:
threadpool: # 线程池配置
corepoolsize: 20 # 核心线程数30:线程池创建时候初始化的线程数
maxpoolsize: 40 # 最大线程数60:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
queuecapacity: 400 # 缓冲队列200:用来缓冲执行任务的队列
keepaliveseconds: 60 # 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
2.加载配置文件
package cn.iocoder.ydtq.framework.async.core;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
@ConfigurationProperties(prefix = "spring.threadpool")
@Validated
@Data
public class GasThreadPoolProperties {
/**
* 线程池创建时候初始化的线程数
*/
@NotNull(message = "线程池创建时候初始化的线程数")
private Integer corepoolsize;
/**
* 线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
*/
@NotNull(message = "线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程")
private Integer maxpoolsize;
/**
* 用来缓冲执行任务的队列
*/
@NotNull(message = "用来缓冲执行任务的队列")
private Integer queuecapacity;
/**
* 当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
*/
@NotNull(message = "当超过了核心线程出之外的线程在空闲时间到达之后会被销毁")
private Integer keepaliveseconds;
}
3.初始化线程池
package cn.iocoder.ydtq.module.gas.check.config;
import cn.iocoder.ydtq.framework.async.core.GasThreadPoolProperties;
import org.slf4j.MDC;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Check线程池 的自动配置类
*
* @author
*/
@Configuration
@EnableConfigurationProperties(GasThreadPoolProperties.class)
public class YdtqCheckThreadPoolAutoConfiguration {
@Resource
private GasThreadPoolProperties gasThreadPoolProperties;
/**
* Check上线程池设置
*
* @return
*/
@Bean(name = "taskExecutorCheck")
public Executor taskExecutorCheck() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数30:线程池创建时候初始化的线程数
executor.setCorePoolSize(ObjectUtils.isEmpty(gasThreadPoolProperties.getCorepoolsize()) ? 5 : gasThreadPoolProperties.getCorepoolsize());
// 最大线程数60:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(ObjectUtils.isEmpty(gasThreadPoolProperties.getMaxpoolsize()) ? 200 : gasThreadPoolProperties.getMaxpoolsize());
// 缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(ObjectUtils.isEmpty(gasThreadPoolProperties.getQueuecapacity()) ? 1 : gasThreadPoolProperties.getQueuecapacity());
// 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(ObjectUtils.isEmpty(gasThreadPoolProperties.getKeepaliveseconds()) ? 60 : gasThreadPoolProperties.getKeepaliveseconds());
// 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("ExecutorCheck-");
// 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,
// 该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务 的 销毁 就会先于 数据库连接池对象
// 的销毁。
executor.setWaitForTasksToCompleteOnShutdown(true);
// 该方法用来设置线程池中 任务的等待时间,如果超过这个时间还没有销毁就 强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
// 使用trace修饰器
executor.setTaskDecorator(new TraceTaskDecorator2());
// 不作为Bean时,手动调用initialize初始化方法,此处可调可不调
// executor.initialize();
return executor;
}
// 修饰器,用于日志打印(如果存在修饰器,则先执行修饰器,在执行原有逻辑)
// 参考地址:https://www.jianshu.com/p/4ec1f0b993cd
class TraceTaskDecorator2 implements TaskDecorator {
public TraceTaskDecorator2() {
}
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
runnable.run();
} finally {
MDC.clear();
}
};
}
}
}
4.使用示例
package cn.iocoder.ydtq.module.gas.check.service.checkreport;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.annotation.Resource;
/**
* 调用方测试Service 类
*
* @author
*/
@Service
public class TestService {
@Resource
private TestAsyncService testAsyncService;
public void test(){
// 获取主线程的上下文信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
testAsyncService.checkReportTest(requestAttributes);
}
}
package cn.iocoder.ydtq.module.gas.check.service.checkreport;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* 被调用方测试Service Async类
*
* @author
*/
@Service
public class TestAsyncService {
@Async("taskExecutorCheck")
public void checkReportTest(RequestAttributes requestAttributes) {
// 保持主线程的上下文信息不丢失
RequestContextHolder.setRequestAttributes(requestAttributes, true);
// TODO 其他业务代码......此处省略
}
}
四.使用注意点
1.如果要保持上下文不丢失:
// 在主线程获取上下文
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 在子线程设置上下文
RequestContextHolder.setRequestAttributes(requestAttributes, true);
2.注意@Async失效场景
2.1异步方法使用static修饰
2.2异步方法返回必须使用void
2.3异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
2.4异步方法不能与异步方法在同一个类中
2.5类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
2.6如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
2.7在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
2.8调用被@Async标记的方法的调用者不能和被调用的方法在同一类中不然不会起作用(如果在一定要注入一个新的bean,使用@Lazy懒加载bean)
3.线程池的自动配置类可以根据业务来配置多个,不要所以的的业务使用同一个bean,避免所以业务积压在同一个线程池。
五.总结
本文核心主要是叫大家如何简单的使用@Async,以及一些失效的场景,部分参数系数的配置可以根据服务器的配置来。