Java全能学习+面试指南:https://javaxiaobear.cn
@Scheduled
注解是Spring Boot提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行,默认是在单线程中执行的
1、注解源码
@Target({
ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
@Reflective
public @interface Scheduled {
String CRON_DISABLED = "-";
String cron() default "";
String zone() default "";
long fixedDelay() default -1L;
String fixedDelayString() default "";
long fixedRate() default -1L;
String fixedRateString() default "";
long initialDelay() default -1L;
String initialDelayString() default "";
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
2、参数说明
参数 | 说明 | 示例 |
---|---|---|
cron | 任务执行的cron表达式 | 0/1 ? |
zone | cron表达时解析使用的时区,默认为服务器的本地时区,使用java.util.TimeZone#getTimeZone(String)方法解析 | GMT-8:00 |
fixedDelay | 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms | 2000 |
fixedDelayString | 上一次任务执行结束到下一次执行开始的间隔时间,使用java.time.Duration#parse解析 | PT15M |
fixedRate | 以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务 | 2000 |
fixedRateString | 与fixedRate逻辑一致,只是使用java.time.Duration#parse解析 | PT15M |
initialDelay | 首次任务执行的延迟时间 | 2000 |
initialDelayString | 首次任务执行的延迟时间,使用java.time.Duration#parse解析 | PT15M |
timeUnit |
3、详解说明
1、cron 参数
表达式格式:
@Scheduled(cron = "{秒数} {分钟} {小时} {日期} {月份} {星期}")
注意: cron表达式可分为6或7个占位符,但在spring自带的定时任务中,cron只支持6个参数,若使用7个参数就会报错
Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'test': Cron expression must consist of 6 fields (found 7 in "*/5 * * * * * *")
代码示例:
/**
* cron 表达式 每隔5秒执行一次
*/
@Scheduled(cron = "*/5 * * * * *")
public void test(){
log.info("小熊学Java是最全Java学习网站!");
}
结果
2023-06-28T15:20:40.009+08:00 INFO 10904 --- [ scheduling-1] com.javaxiaobear.job.MyScheduledJob : 小熊学Java是最全Java学习网站!
2023-06-28T15:20:45.014+08:00 INFO 10904 --- [ scheduling-1] com.javaxiaobear.job.MyScheduledJob : 小熊学Java是最全Java学习网站!
2023-06-28T15:20:50.004+08:00 INFO 10904 --- [ scheduling-1] com.javaxiaobear.job.MyScheduledJob : 小熊学Java是最全Java学习网站!
2023-06-28T15:20:55.010+08:00 INFO 10904 --- [ scheduling-1] com.javaxiaobear.job.MyScheduledJob : 小熊学Java是最全Java学习网站!
2023-06-28T15:21:00.002+08:00 INFO 10904 --- [ scheduling-1] com.javaxiaobear.job.MyScheduledJob : 小熊学Java是最全Java学习网站!
更多的cron表达式,自行测试哈,这里不做过多演示
“30 * * * * ?” 每半分钟触发任务
“30 10 * * * ?” 每小时的10分30秒触发任务
“30 10 1 * * ?” 每天1点10分30秒触发任务
“30 10 1 20 * ?” 每月20号1点10分30秒触发任务
“30 10 1 20 10 ? *” 每年10月20号1点10分30秒触发任务
“30 10 1 20 10 ? 2011” 2011年10月20号1点10分30秒触发任务
“30 10 1 ? 10 * 2011” 2011年10月每天1点10分30秒触发任务
“30 10 1 ? 10 SUN 2011” 2011年10月每周日1点10分30秒触发任务
“15,30,45 * * * * ?” 每15秒,30秒,45秒时触发任务
“15-45 * * * * ?” 15到45秒内,每秒都触发任务
“15/5 * * * * ?” 每分钟的每15秒开始触发,每隔5秒触发一次
“15-30/5 * * * * ?” 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
“0 0/3 * * * ?” 每小时的第0分0秒开始,每三分钟触发一次
“0 15 10 ? * MON-FRI” 星期一到星期五的10点15分0秒触发任务
“0 15 10 L * ?” 每个月最后一天的10点15分0秒触发任务
“0 15 10 LW * ?” 每个月最后一个工作日的10点15分0秒触发任务
“0 15 10 ? * 5L” 每个月最后一个星期四的10点15分0秒触发任务
“0 15 10 ? * 5#3” 每个月第三周的星期四的10点15分0秒触发任务
2、fixedDelay 参数
fixedDelay 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms
代码示例:
/**
* fixedDelay 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms
* 每隔2秒执行一次
*/
@Scheduled(fixedDelay = 2000)
public void testFixedDelay (){
log.info("小熊学Java是最全Java学习网站!");
}
结果展示:
3、fixedRate参数
以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务
/**
* 以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若
* 在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务
* 每隔2秒执行一次
*/
@Scheduled(fixedRate = 2000)
public void testFixedRate () throws InterruptedException {
Thread.sleep(3000);
log.info("小熊学Java是最全Java学习网站!");
}
日志每3秒执行一次,这也是因为@Scheduled是在单线程中执行的
4、initialDelay参数
首次任务执行的延迟时间
/**
* initialDelay 首次任务执行的延迟时间
* 每隔2秒执行一次
*/
@Scheduled(fixedRate = 2000, initialDelay = 3000)
public void testInitialDelay (){
log.info("小熊学Java是最全Java学习网站!");
}
首次延迟的时间是3秒,之后每2秒执行一次
4、@Scheduled多线程
1、场景演示
执行以下两个方法
@Scheduled(fixedRate = 2000)
public void test1 () throws InterruptedException {
Thread.sleep(3000);
log.info("小熊学Java 是最全Java学习网站!---test1");
}
@Scheduled(fixedRate = 2000)
public void test2 () throws InterruptedException {
Thread.sleep(3000);
log.info("小熊学Java是最全Java学习网站!---test2");
}
从执行结果中可以看出,test1方法和test2方法交替输出日志,并没有同时执行
org.springframework.scheduling.config.ScheduledTaskRegistrar
源码发现
protected void scheduleTasks() {
//如果为空,则以单线程执行
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
Iterator var1;
if (this.triggerTasks != null) {
var1 = this.triggerTasks.iterator();
while(var1.hasNext()) {
TriggerTask task = (TriggerTask)var1.next();
this.addScheduledTask(this.scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
var1 = this.cronTasks.iterator();
while(var1.hasNext()) {
CronTask task = (CronTask)var1.next();
this.addScheduledTask(this.scheduleCronTask(task));
}
}
IntervalTask task;
if (this.fixedRateTasks != null) {
var1 = this.fixedRateTasks.iterator();
while(var1.hasNext()) {
task = (IntervalTask)var1.next();
if (task instanceof FixedRateTask) {
FixedRateTask fixedRateTask = (FixedRateTask)task;
this.addScheduledTask(this.scheduleFixedRateTask(fixedRateTask));
} else {
this.addScheduledTask(this.scheduleFixedRateTask(new FixedRateTask(task)));
}
}
}
if (this.fixedDelayTasks != null) {
var1 = this.fixedDelayTasks.iterator();
while(var1.hasNext()) {
task = (IntervalTask)var1.next();
if (task instanceof FixedDelayTask) {
FixedDelayTask fixedDelayTask = (FixedDelayTask)task;
this.addScheduledTask(this.scheduleFixedDelayTask(fixedDelayTask));
} else {
this.addScheduledTask(this.scheduleFixedDelayTask(new FixedDelayTask(task)));
}
}
}
}
当未手动指定taskScheduler
时,会通过Executors.newSingleThreadScheduledExecutor()
创建默认的单线程线程池,且该线程池的拒绝策略为AbortPolicy
,这种策略在线程池无可用线程时丢弃任务,并抛出异常RejectedExecutionException
。
2、多线程配置
1、配置bean
在启动类中,配置bean,代码如下:
/**
* 配置线程池
* @return
*/
@Bean
public TaskScheduler config(){
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
//线程池大小为10
taskScheduler.setPoolSize(10);
return taskScheduler;
}
2、配置类
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//Scheduler指定线程池
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
运行结果跟上面一样,每3秒同时执行
5、Async异步执行
异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。
1、简单使用
Spring Boot的异步任务,只需在方法上添加异步注解,同时开启异步任务
@Component
@Slf4j
public class AsyncTask {
@Async
public void test(){
log.info("小熊学Java 是最棒的!!!");
}
}
方法调用
@Resource
private AsyncTask asyncTask;
@Scheduled(fixedRate = 2000)
public void test1 () throws InterruptedException {
asyncTask.test();
Thread.sleep(3000);
log.info("小熊学Java 是最全Java学习网站!---test1");
}
结果输出
2、异步失效
1、异步方法和调用异步方法在同一个类中
有时候,经常看到编写异步任务,都是这样写的
@Scheduled(fixedRate = 2000)
public void test1 () throws InterruptedException {
asyncTask.test();
Thread.sleep(3000);
log.info("小熊学Java 是最全Java学习网站!---test1");
}
@Async
public void test(){
log.info("小熊学Java 是最棒的!!!");
}
这样写是不会生效的,由于@Async的AdviceMode默认为PROXY,所以当调用方和被调用方是在同一个类中,无法产生切面,@Async没有被Spring容器管理,可以查看源码,具体详情可参考这篇:https://juejin.cn/post/6976893903223914527#heading-5