spring提供了@Scheduled
和@EnableScheduling
两个注解用来快速开发定时器,使用很简单
用法: 1、Springboot的启动类中加上@EnableScheduling
注解。
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2、需要定时执行的方法上加上@Scheduled注解,这个注解中可以指定定时执行的规则。
案例
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Component
public class Test {
//每秒执行一次
@Scheduled(fixedRate = 1000)
public void schedule(){
System.out.println("定时执行任务: " + System.currentTimeMillis());
}
}
启动springboot项目,运行查看日志输出,每秒会输出一次:
定时执行任务: 1661825432538
定时执行任务: 1661825433540
定时执行任务: 1661825434538
定时规则
@Scheduled可以用来配置定时器的执行规则,@Scheduled中主要有9个参数,
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String CRON_DISABLED = ScheduledTaskRegistrar.CRON_DISABLED;
String cron() default "";
String zone() default "";
long fixedDelay() default -1;
String fixedDelayString() default "";
long fixedRate() default -1;
String fixedRateString() default "";
long initialDelay() default -1;
String initialDelayString() default "";
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
1、cron
该参数接收一个cron表达式,cron表达式是一个字符串,字符串是以5或6个空格隔开,分开共6或7个域,每个域代表一个含义
语法
[秒] [分] [小时] [日] [月] [周] [年]
注:其中[年]
不是必须的域,可以省略[年]
域 | 必填 | 允许值 | 允许通配符 |
秒 | 是 | 0-59 | , – * / |
分 | 是 | 0-59 | , – * / |
时 | 是 | 0-23 | , – * / |
日 | 是 | 1-31 | , – * ? / L W |
月 | 是 | 1-12/JAN-DEC | , – * / |
周 | 是 | 1-7/SUM-SAT | , – * ? / L # |
年 | 否 | 1970-2099 | , – * / |
通配符
*
:表示所有值,例如在分字段上设置*,表示每一分钟都会触发
?
:表示不指定值,使用的为不需要关心当前设置这个字段的值,例如,要在每月的5号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为?,具体设置为 0 0 0 5 * ?
-
:表示区间,例如在小时上设置”10-12″,表示10,11,12点都会触发
,
:表示指定多个值,例如在周字段上设置”MON,WED,FRI”表示周一,周三和周五触发
/
:用于递增触发,如在秒上面设置”5/15″表示从5秒开始,每增15秒触发(5,20,35,50),在日字段上设置’1/3’所示每月1号开始,每隔三天触发一次。
L
:表示最后的意思,在日字段的设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年,在周字段上表示星期六,相当于7
或SAT
,如果在L
前加上数字,则表示该数据的最后一个,例如在周字段上设置6L
这样的格式,则表示本月最后一个星期五)
W
:表示离指定日期的最近那个工作日(周一至周五),例如在日字段上置15W
,表示离每月15号最近的那个工作日触发,如果15号正好是周六,则找最近的周五(14号)触发,如果15号是周日,则找最近的下周一(16日)触发,如查15号正好是工作日(周一至再周五),则就在该天触发,如果指定格式为1W
,它则表示每月1号往后最近的工作日触发,如果1号是周六,则将在3号即下周一触发,W
前只能设置具体的数字,不允许区间-
#
:序号(表示每月的第几个周几),例如在周字段上设置“6#3”表示在每月的第三个周六,L
和W
可以组合使用,如果在日字段上设置LW
,则表示在本月的最后一个工作日触发,
示例
每隔5秒执行一次
*/5 * * * * ?
每隔1分钟执行一次
0 */1 * * * ?
每天23点执行一次
0 0 23 * * ?
每天凌晨1点执行一次
0 0 1 * * ?
每月1号凌晨1点执行一次
0 0 1 1 * ?
每月最后一天23点执行一次
0 0 23 L * ?
每周星期六凌晨1点执行一次
0 0 1 ? * L
在26分,29分,33分执行一次
0 26,29.33 * * * ?
每天的0点,13点,18点,21点都执行一次
0 0 0,13,18,21 * * ?
2、fixedDelay
表示上一次执行完毕时间点之后多长时间执行
@Scheduled(fixedDelay = 5000) //上一次执行完毕时间点之后5秒执行
3、fixedDelayString
跟fixedDelay
一样,只不过fixedDelayString
使用字符串的形式,另外fixedDelayString
支持占位符
@Scheduled(fixedDelayString = "5000") //上一次执行完毕时间点之后5秒执行
4、fixedRate
表示上次执行时间点之后多长时间再执行
@Scheduled(fixedRate = 5000) //上一次执行时间点之后5秒执行
5、fixedRateString
与fixedRate
所表示的意思一样,只不过fixedRateString
使用的是字符串,同时fixedRateString
支持占位符
6、initialDelay
第一次延迟多长时间后再执行
//第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
@Scheduled(initialDelay = 1000, fixedRate = 5000)
自定义线程池
默认情况下,只有一个线程来执行定时器的任务,在只一个线程的情况下,如果有多个定时任务需要执行,就会出现定时任务排队使用,如果有线定时任务的执行时间过长,就会导致其它任务的执行不能按照既定的时间进行执行,如:
@Component
public class Test {
//每秒执行一次
@Scheduled(fixedRate = 1000)
public void schedule(){
System.out.println(Thread.currentThread() + " 定时执行任务1: " + System.currentTimeMillis());
}
@Scheduled(fixedRate = 1000)
public void se2(){
System.out.println(Thread.currentThread() + " 定时执行任务2: " + System.currentTimeMillis());
}
@Scheduled(fixedRate = 1000)
public void se3() throws InterruptedException{
//耗时2秒,模拟耗时操作
Thread.sleep(2000);
System.out.println(Thread.currentThread() + " 定时执行任务3: " + System.currentTimeMillis());
}
}
运行输出:
Thread[scheduling-1,5,main] 定时执行任务3: 1661910086110
Thread[scheduling-1,5,main] 定时执行任务1: 1661910086110
Thread[scheduling-1,5,main] 定时执行任务2: 1661910086110
Thread[scheduling-1,5,main] 定时执行任务3: 1661910088111
Thread[scheduling-1,5,main] 定时执行任务1: 1661910088111
Thread[scheduling-1,5,main] 定时执行任务2: 1661910088111
从输出结果可以看出,由于se3
的定时任务执行任务花费了2秒,从而导致了其它两个定时任务的执行时间也间隔了2秒。这就是只有一个线程执行定时任务排队执行任务所造成的结果,
为了解决定时任务排队的问题,我们可以通过自定义线程池来解决这个问题
@Configuration
public class ScheduleConfig {
@Bean
public ScheduledExecutorService scheduledExecutorService(){
//设置并行执行的任务数量
int corePoolSize = 10;
return new ScheduledThreadPoolExecutor(corePoolSize);
}
}
再次运行输出:
Thread[pool-1-thread-2,5,main] 定时执行任务1: 1661910879971
Thread[pool-1-thread-4,5,main] 定时执行任务2: 1661910880971
Thread[pool-1-thread-5,5,main] 定时执行任务1: 1661910880971
Thread[pool-1-thread-1,5,main] 定时执行任务3: 1661910880971
Thread[pool-1-thread-2,5,main] 定时执行任务2: 1661910881971
Thread[pool-1-thread-1,5,main] 定时执行任务1: 1661910881971
Thread[pool-1-thread-6,5,main] 定时执行任务3: 1661910882973
从运行输出结果可以看出,任务1,2,3分别运行于不同的线程中,因此执行互不影响。
原创文章,作者:jiafegn,如若转载,请注明出处:https://www.techlearn.cn/archives/492