springboot定时任务与线程池配置.md
0、前言
在 Spring Boot 中,定时任务的执行可以通过 @Scheduled 注解来实现。为了更好地管理和优化多任务并发执行,配置合适的线程池是非常重要的。本文将介绍如何在 Spring Boot 中配置定时任务,并通过配置文件或编程方式设置线程池。
一、启用定时任务支持
在 Spring Boot 应用的主类中,使用 @EnableScheduling 注解来启用定时任务支持。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
注:@EnableScheduling不一定只能放在Application.java中,也可以放在其他Bean上。比如后面的SchedulerConfig.java,自选放一个位置即可。
@Configuration
@EnableScheduling
public class SchedulerConfig {
}
二、定义定时任务
在 Spring Boot 中,可以通过 @Scheduled 注解来定义定时任务。以下示例展示了一个每隔 5 秒执行一次的任务:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
System.out.println("Current Time: " + System.currentTimeMillis());
}
}
注:@Scheduled可以是cron表达式,以下为一些使用方式:
fixedRate
(固定速率)
@Scheduled(fixedRate = X)
表示任务以固定速率执行,任务之间的间隔时间是固定的,不考虑任务的执行时间。
@Scheduled(fixedRate = 5000)
public void taskWithFixedRate() {
System.out.println("Task with fixed rate - " + System.currentTimeMillis());
}
上面的示例表示任务每 5 秒执行一次,无论前一个任务是否完成。
fixedDelay
(固定延迟)
@Scheduled(fixedDelay = X)
表示任务在前一个任务完成之后,再等待固定的时间间隔执行下一次任务。
@Scheduled(fixedDelay = 5000)
public void taskWithFixedDelay() {
System.out.println("Task with fixed delay - " + System.currentTimeMillis());
}
此示例表示任务每 5 秒执行一次,但每次执行会等待前一个任务完成后,再延迟 5 秒。
initialDelay
(初始延迟)
@Scheduled(initialDelay = X, fixedRate = Y)
或 @Scheduled(initialDelay = X, fixedDelay = Y)
允许你在任务开始前指定一个初始延迟时间。
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void taskWithInitialDelay() {
System.out.println("Task with initial delay - " + System.currentTimeMillis());
}
此示例表示任务将在启动后等待 10 秒,然后每 5 秒执行一次。
- 使用 Cron 表达式
@Scheduled(cron = "cron_expression")
可以使用 Cron 表达式来精确地定义任务的执行时间。
@Scheduled(cron = "0 0/5 * * * ?")
public void taskWithCronExpression() {
System.out.println("Task with cron expression - " + System.currentTimeMillis());
}
上面的示例表示任务每 5 分钟执行一次。
三、配置线程池
默认的定时任务是单线程的,可用于测试,但如果用于生产环境,建议进行线程池配置以适应于正常环境中使用。
1.方式一:通过配置文件
Spring Boot 提供了一些默认的属性来配置定时任务的线程池。通过 application.properties 或 application.yml 文件可以方便地进行配置。
- 使用 application.properties
# 设置线程池的大小
spring.task.scheduling.pool.size=10
# 设置线程名称的前缀
spring.task.scheduling.thread-name-prefix=Scheduler-
# 设置等待终止的秒数
spring.task.scheduling.shutdown.await-termination=true
spring.task.scheduling.shutdown.await-termination-period=60s
- 使用 application.yml
spring:
task:
scheduling:
pool:
size: 10 # 设置线程池的大小
thread-name-prefix: Scheduler- # 设置线程名称的前缀
shutdown:
await-termination: true # 是否等待所有任务完成再终止
await-termination-period: 60s # 等待终止的秒数
2.方式二:配置线程池(使用编程方式)
如果需要更灵活的配置,可以在配置类中定义 ThreadPoolTaskScheduler:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 设置线程池大小
scheduler.setThreadNamePrefix("Scheduler-");// 线程名称前缀
scheduler.setAwaitTerminationSeconds(60); //在应用关闭时,如果不希望立即中断正在执行的任务,可以设置等待终止的时间,让任务有机会完成
scheduler.setWaitForTasksToCompleteOnShutdown(true); //是否等待所有任务完成再终止
return scheduler;
}
}
四、任务的异常处理
默认情况下,如果定时任务抛出未捕获的异常,任务将不会继续执行。因为建议在编写定时任务时进行处理异常:
- 未进行异常处理场景
@Scheduled(cron = "0/5 * * * * ?")
public void deleteSysLog() {
int result = 1 / 0; // 故意模拟一个异常来演示
log.info("{}", result);
}
比如上面的定时任务,会在执行过程中出现异常,java.lang.ArithmeticException: / by zero,但定时任务的方法没有进行异常处理,定时任务在异常发生后,后续不再会进行任务调度,因此建议为保持任务的持续调度,需要在定义定时任务的方法中进行异常处理。。
- 改造后
@Scheduled(cron = "0/5 * * * * ?")
public void deleteSysLog() {
try {
int result = 1 / 0;
log.info("{}", result);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
改造后的定时任务,即使任务出现异常后,下一轮仍然会继续调度。
五、案例进行sys_log表的日志定时清理
比如我的web应用程序有一张sys_log表,每天会产生操作日志数据,有begin_time字段,我可以通过定义每小时的定时任务进行日志清理,保留3个月内的日志数据
1.定义定时任务类
package cn.gzsendi.modules.system.scheduler;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import cn.gzsendi.modules.framework.utils.RedissonLockUtils;
import cn.gzsendi.modules.system.mapper.SysLogMapper;
import lombok.extern.slf4j.Slf4j;
/**
* 定义日志清理定时任务任务配置类
* syslog.clear.enabled: 是否开启日志清理定时任务
* matchIfMissing:如果没有找到配置就默认禁用
*/
@Configuration
@ConditionalOnProperty(name = "syslogclear.enabled", havingValue = "true", matchIfMissing = false)
@EnableScheduling
@Slf4j
public class SyslogScheduer {
/**
* 引入日志操作mapper,用于sys_log表的数据清理
*/
@Resource
private SysLogMapper sysLogMapper;
/**
* 每小时执行一次定时任务,清理3个月前的sys_log表数据
*/
@Scheduled(cron = "0 0 * * * ?")
public void deleteSysLog() {
// 执行日志清除
try {
int effectRows = sysLogMapper.deleteBeforeThreeMonths();
log.info("清理sys_log任务执行完成,删除日志数据 {} 条.", effectRows);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
说明:@ConditionalOnProperty(name = “syslogclear.enabled”, havingValue = “true”, matchIfMissing = true)
-
name = “syslogclear.enabled”: 这是要检查的属性名。如果该属性在配置文件(例如 application.properties 或 application.yml)中存在,则其值将用于条件判断。
-
havingValue = “true”: 这是 name 属性对应的值,当配置文件中的属性值与此处的值匹配时,条件为 true,则组件会被加载。
-
matchIfMissing = true: 如果配置文件中没有定义 syslogclear.enabled 这个属性,则默认认为条件为 true,因此组件会被加载。也就是说,如果属性缺失,将使用默认值(这里是 true)来判断是否启用组件。
SysLogMapper.java,采用Mybatis定义一个清理日志接口方法
/**
* 删除3个月前的日志
* @return
*/
public int deleteBeforeThreeMonths();
SysLogMapper.xml,对应的sql语句
<delete id="deleteBeforeThreeMonths" >
DELETE FROM sys_log WHERE begin_time < NOW() - INTERVAL '3 months'
</delete>
2.修改配置文件application.yml
修改配置文件并支持定时任务线程池处理
# application.yml中增加
spring:
task:
scheduling:
pool:
size: 10 # 设置线程池的大小
thread-name-prefix: Scheduler- # 设置线程名称的前缀