文章目录
- 一、前言
- 二、案例展示
- 1、初始版本
- 2、代码审核意见和优化建议
- 3、潜在问题和风险
- 4、优化建议
- 5、优化后的代码
- 三、具体使用
一、前言
- 有时候我们在项目中做一些长链路的跑批任务时,基于Springboot项目的定时任务,我们可以指定一个自定义的线程配置类进行单独提供给具体跑批任务使用,而不占用整个系统资源。
二、案例展示
- 我们观察一下第一种方式和优化后的对比在哪里,关键点什么?
1、初始版本
@Configuration
@EnableAsync
public class ScheduledConfig {
@Bean("baseExecutor")
public Executor fastExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("xxxx-schedule-baseExecutor");
executor.setMaxPoolSize(10);
executor.setCorePoolSize(8);
executor.setQueueCapacity(5);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
2、代码审核意见和优化建议
3、潜在问题和风险
- 异常处理: 线程池的默认异常处理机制可能不足以处理所有任务抛出的异常。尽管设置了CallerRunsPolicy作为拒绝执行处理器,它仅在线程池饱和时工作,并不直接处理任务执行中的异常。建议增强异常处理逻辑,例如通过自定义的RejectedExecutionHandler实现或者在任务代码中加入更细粒度的异常处理。
- 资源泄露: 当应用停止时,如果线程池没有被正确关闭,可能会导致资源泄露。虽然Spring通常会管理Bean的生命周期,但最好确认ThreadPoolTaskExecutor的实例是否加入了适当的销毁逻辑,例如调用executor.shutdown()。
- 线程数配置: CorePoolSize被设置为8,MaxPoolSize被设置为10,这意味着在高负载情况下,线程池只能扩展到10个线程。这样的配置可能不足以处理所有高峰时段的请求。你需要根据实际的负载测试结果调整这些参数。
- 队列容量: 队列容量被固定为5。这意味着一旦队列满了,即使线程池还没有达到最大大小,任务也会被拒绝执行。根据实际的负载情况,考虑适当增加队列容量或调整线程池的大小。
4、优化建议
- 性能监控: 考虑添加监控机制来跟踪线程池的性能指标,如活跃线程数、队列大小和拒绝执行的任务数等。这有助于及时发现和调整配置参数,以保证系统的稳定性。
- 可调整性: 考虑将线程池的配置参数(如corePoolSize, maxPoolSize, queueCapacity等)设置为可从外部配置(例如应用配置文件)的方式获取。这样可以在不修改代码的情况下调整这些参数,以适应不同的运行环境。
- 定制化: 如果有更复杂的需求,比如需要执行周期性任务或需要更细粒度的控制任务执行,可以考虑使用Spring提供的其他任务调度组件,如 ScheduledThreadPoolExecutor 或者集成 Quartz 等第三方库。
5、优化后的代码
@Configuration
@EnableAsync
public class ExecutorConfig {
@Bean("baseExecutor")
public Executor fastExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("xxxx-schedule-base");
executor.setCorePoolSize(16);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setRejectedExecutionHandler(new CustomRejectedExecutionHandler());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
executor.shutdown();
}));
return executor;
}
static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("Task " + r.toString() + " rejected from executor " + executor.toString());
}
}
}
三、具体使用
@Async("baseExecutor")
@Scheduled(cron = "0 0/5 * * * ?"){
}