[Java]SpringBoot实现排程(定时任务)、cron表达式介绍、常见问题解决

2021-07-10 83点热度 0人点赞 0条评论

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

@yearly (or @annually)

once a year (0 0 0 1 1 *)

@monthly

once a month (0 0 0 1 * *)

@weekly

once a week (0 0 0 * * 0)

@daily (or @midnight)

once a day (0 0 0 * * *), or

@hourly

once an hour, (0 0 * * * *)

常见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) {
                }
            }
        }
    
    }
    

     

admin

这个人很懒,什么都没留下

文章评论

*

code