Spring或SpringBoot自带了基本的排程功能,SpringMVC实现排程,请查看:https://blog.terrynow.com/2021/08/03/java-springmvc-schedule-implement/
MyApplication.java启动类增加Schedule功能
@SpringBootApplication @EnableScheduling public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
实现排程,新增ScheduledTask.java类
在方法上使用@Scheduled注释,可以让方法定时执行,以下是注解的说明
initial-delay : 表示第一次执行前需要延迟的时间,单位毫秒
fixed-delay : 表示从上一个任务完成到下一个任务开始的间隔, 单位毫秒
fixed-rate : 表示从上一个任务开始到下一个任务开始的间隔, 单位毫秒。 (如果上一个任务执行超时,则可能是上一个任务执行完成后立即启动下一个任务)
cron : cron 表示式,支持更复杂的定时安排。
@Component public class ScheduledTask { private static final Log log = LogFactory.getLog(ScheduledTask.class); // 如果要操作数据库或者引入Service @Resource(name = "systemService") private ISystemService systemService; //每一分钟执行一次 @Scheduled(cron = "0 */1 * * * ?") public void test1() { // your code } @Scheduled(fixedDelay = 5000) public void test2() { // your code } /** * 每个10分的延后3分执行 * spring 的 cron 和 linux 的 cron 相比,好像spring多了第一位是秒 */ @Scheduled(cron = "0 3/10 * * * ?") public void test3() { // your code } /** * 每天凌晨1:04触发 */ @Scheduled(cron = "0 4 1 ? * *") public void test4() { // your code } }
cron表达式介绍
Cron表示式
Cron表示式是一个字符串,是由空格隔开的6或7个域组成,每一个域对应一个含义(秒 分 时 每月第几天 月 星期 年)其中年是可选栏位。
┌───────────── 秒 second (0-59) │ ┌───────────── 分钟 minute (0 - 59) │ │ ┌───────────── 小时 hour (0 - 23) │ │ │ ┌───────────── 天 day of the month (1 - 31) │ │ │ │ ┌───────────── 月 month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── 星期 day of the week (0 - 7) │ │ │ │ │ │ (0或者7是周日 0 or 7 is Sunday, or MON-SUN) │ │ │ │ │ │ * * * * * *
-
每个域可出现的类型和各字符的含义
秒:可出现: ”, – * /” 左列的四个字符,有效范围为0-59的整数
分:可出现: ”, – * /” 左列的四个字符,有效范围为0-59的整数
时:可出现: ”, – * /” 左列的四个字符,有效范围为0-23的整数
每月第几天:可出现: ”, – * / ? L W C” 左列的八个字符,有效范围为0-31的整数
月:可出现: ”, – * /” 左列的四个字符,有效范围为1-12的整数或JAN-DEC
星期:可出现: ”, – * / ? L C #” 左列的八个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
特殊字符含义
- * : 表示匹配该域的任意值,比如在秒*, 就表示每秒都会触发事件。;
- ? : 只能用在每月第几天和星期两个域。表示不指定值,当2个子表示式其中之一被指定了值以后,为了避免冲突,需要将另一个子表示式的值设为“?”;
- – : 表示范围,例如在分域使用5-20,表示从5分到20分钟每分钟触发一次
- / : 表示起始时间开始触发,然后每隔固定时间触发一次,例如在分域使用5/20,则意味着5分,25分,45分,分别触发一次.
- , : 表示列出列举值。例如:在分域使用5,20,则意味着在5和20分时触发一次。
- L : 表示最后,只能出现在星期和每月第几天域,如果在星期域使用1L,意味著在最后的一个星期日触发。
- W : 表示有效工作日(週一到週五),只能出现在每月第几日域,系统将在离指定日期的最近的有效工作日触发事件。注意一点,W的最近寻找不会跨过月份
- LW : 这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
- # : 用于确定每个月第几个星期几,只能出现在每月第几天域。例如在1#3,表示某月的第三个星期日。
宏Macros
You can use these macros instead of the six-digit value, thus:
@Scheduled(cron = "@hourly")
.
Macro | Meaning |
---|---|
|
once a year ( |
|
once a month ( |
|
once a week ( |
|
once a day ( |
|
once an hour, ( |
常见cron表达式举例
0 0 12 * * ? 每天12点触发
0 15 10 ? * * 每天10点15分触发
0 15 10 * * ? 每天10点15分触发
0 15 10 * * ? * 每天10点15分触发
0 15 10 * * ? 2005 2005年每天10点15分触发
0 * 14 * * ? 每天下午的 2点到2点59分每分触发
0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED 3月分每周三下午的 2点10分和2点44分触发
0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发
0 15 10 15 * ? 每月15号上午10点15分触发
0 15 10 L * ? 每月最后一天的10点15分触发
0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发
0 15 10 ? * 6L 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每月的第三周的星期五开始触发
0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)
Scheduled常见问题
- 多个排程任务同时执行时候,只有一个任务在执行,执行完了以后,才会继续下一个;列队执行任务丢失,转为非同步排程
预设的 ConcurrentTaskScheduler 计划执行器采用Executors.newSingleThreadScheduledExecutor() 实现单列队的执行器。因此,对同一个排程任务的执行总是同一个列队。如果任务的执行时间超过该任务的下一次执行时间,则会出现任务丢失,跳过该段时间的任务。
上述问题有以下解决办法:采用非同步的方式执行排程任务,配置Spring 的@EnableAsync,在执行定时任务的方法上标注@Async配置任务执行池,列队线程池大小n 的数量为单个任务执行所需时间/ 任务执行的间隔时间。如下:@SpringBootApplication @EnableScheduling // 需要加@EnableAsync才能使用@Async @EnableAsync public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
@Component public class ScheduledTask { private static final Log log = LogFactory.getLog(ScheduledTask.class); //每30秒執行一次 @Async() @Scheduled(fixedRate = 1000 * 3) public void testSchedule0() { System.out.println("testSchedule0 called: " + Thread.currentThread().getName()); } /** * 测试8点21分执行,观察1分钟后和testSchedule2是否会同时执行 */ @Scheduled(cron = "0 21 8 ? * *") @Async public void testSchedule1() { for (int i = 0; i < 70; i++) { log.error("testSchedule1 called, " + Thread.currentThread().getName()); try { Thread.sleep(1000L); } catch (InterruptedException ignored) { } } } /** * 测试8点22分执行 */ @Scheduled(cron = "0 22 8 ? * *") @Async public void testSchedule2() { for (int i = 0; i < 70; i++) { log.warn("testSchedule2 called, " + Thread.currentThread().getName()); try { Thread.sleep(1000L); } catch (InterruptedException ignored) { } } } }
文章评论