今天来分享一下使用Spring Boot线程池,并且在每次执行任务的时候打印线程池的状态日志
Spring Boot线程池的基本用法
主要分为以下几个步骤:
开启异步线程
在项目入口加上注解@EnableAsync
@SpringBootApplication
@EnableAsync // 增加该注解
public class RootCauseInferenceServiceApplication {
public static ApplicationContext applicationContext;
public static void main(String[] args) {
applicationContext = SpringApplication.run(RootCauseInferenceServiceApplication.class, args);
}
}
线程池参数配置
@Configuration
public class AsyncScheduledTaskConfig {
// 核心线程池大小
public static int corePoolSize = 10;
// 最大可创建的线程数
// @Value("${async.executor.thread.max_pool_size}")
private int maxPoolSize = 10;
// 队列最大长度
// @Value("${async.executor.thread.queue_capacity}")
private int queueCapacity = 10;
// 线程池维护线程所允许的空闲时间
// @Value("${async.executor.thread.keep_alive_seconds}")
private int keepAliveSeconds = 300;
// 线程池名前缀
// @Value("${async.executor.thread.threadNamePrefix}")
private static final String threadNamePrefix = "Async-Service-";
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new MyExecutor(); // 继承ThreadPoolTaskExecutor自定义的线程池
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略
// 初始化
executor.initialize();
return executor;
}
}
定义任务
@Service
public class AsyncService {
@Async("threadPoolTaskExecutor") // 使用该注解,会从线程池取线程异步执行该方法
public void runTask() {
// 具体任务
}
}
执行任务
@Service
public class InferenceService {
@Resource
private AsyncService asyncService;
public InferenceDto inference() {
for (int i = 0; i < 10; i++) {
asyncService.runTask(); // 调用执行任务,此处要注意runTask不能与调用方在同一个类里面
}
return new InferenceDto();
}
}
以上就是使用Spring Boot线程池的基本步骤,但是我们无法监控到线程池的运行状态;下面是使用面向切面编程的方式在每次执行任务时打印线程池的状态日志
自定义线程池
@Slf4j
public class MyExecutor extends ThreadPoolTaskExecutor { // 继承ThreadPoolTaskExecutor
public void showThreadPoolInfo() { // 打印线程池的状态日志
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
log.info("taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
threadPoolExecutor.getTaskCount(), threadPoolExecutor.getCompletedTaskCount(),
threadPoolExecutor.getActiveCount(), threadPoolExecutor.getQueue().size());
}
@Override
public <T> Future<T> submit(Callable<T> task) { // 重写submit方法,实际上什么也没有干,但是必须重写,否则无法实现面向切面编程
return super.submit(task);
}
}
定义切面
@Aspect
@Component
public class ExecutorAspect {
// 在线程池执行submit方法时打印线程池的状态
@Before("execution(* com.janson.rootcauseinferenceservice.executor.MyExecutor.submit(..))")
public void showThreadPoolInfo() {
MyExecutor myExecutor =
(MyExecutor) RootCauseInferenceServiceApplication.applicationContext.getBean("threadPoolTaskExecutor"); // 获取到线程池的Bean对象
myExecutor.showThreadPoolInfo();
}
}
那么问题来了,为什么不直接在重写submit方法内部打印线程池的状态呢?
因为我还没有深入了解,线程池是不是只会调用这个submit方法,如果是调用其他方法,我们要在每个submit/execute方法里面都调用一次showThreadPoolInfo()方法吗?!
后续可以深入研究一下线程池实现原理~