定时任务:指的是应用程序在指定的时间执行预先定义好的程序片段

使用SpringBoot创建定时任务,有以下三种创建方式

  • 基于注解(@Scheduled)
  • 基于接口(SchedulingConfigurer)
    基于接口创建的定时器可以动态的从数据库中读取指定时间来动态执行定时任务
  • 基于注解创建多线程定时任务

静态:基于注解

只需要创建定时任务配置类即可

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   //2.开启定时任务
@ComponentScan(basePackageClasses ={},basePackages = {})  //指定定时任务在哪个包或者哪个类有效
public class StaticScheduleTaskConfig {
    //3.添加定时任务
    @Scheduled(cron = "0/5 * * * * ?")
    //或直接指定时间间隔,例如:5秒
    //@Scheduled(fixedRate=5000)
    public void configureTasks() {
        System.out.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
}

@Scheduled注解标注的方法,表示该方法是定时任务方法,该注解中可以填:
Cron表达式,该表达式表示按照什么规则去执行定时任务,例如以下规则

"*/5 * * * * ?",表示每5秒执行一次任务
"0 */1 * * * ?",表示每隔1分钟执行一次
"0 0 23 * * ?",表示每天23点执行一次
"0 0 1 * * ?",表示每天凌晨1点执行一次
"0 0 1 1 * ?",表示每月1号凌晨1点执行一次
"0 0 23 L * ?",表示每月最后一天23点执行一次
"0 0 1 ? * L",表示每周星期天凌晨1点实行一次
"0 26,29,33 * * * ?",表示在26分、29分、33分执行一次
"0 0 0,13,18,21 * * ?",表示每天的0点、13点、18点、21点都执行一次

规则是:每个*分别表示:秒(0-59),分(0-59),时(0-23),日(0-31)的某天,月(0-11),周几(1-7)
每个*的位置可以填具体值,或者以下符号:
	*:所有值都匹配
    ?:无所谓,不关心,通常放在“周几”里
    ,:或者
    /:增量值
    -:区间

值得注意的是,/表示增量值,例如:
	"*/10 * * * * *",表示每10秒
	"0 5/15 * * * *",表示每小时的5分、20分、35分、50分

@Scheduled注解除了可以填Corn表达式,还可以使用fixedRatefixedDelay两个属性

  • fixedDelay设置的是:上一个任务结束后多久执行下一个任务
  • fixedRate设置的是:上一个任务的开始到下一个任务开始时间的间隔

注意:如果是强调任务间隔的定时任务,建议使用fixedRate和fixedDelay,
如果是强调任务在某时某分某刻执行的定时任务,建议使用Cron表达式。

另外,基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。
什么意思呢?让我们来看个例子。

@Configuration      
@EnableScheduling   
public class StaticScheduleTaskConfig {

    @Scheduled(cron = "0/1 * * * * ?")
    public void test1() throws InterruptedException {
        System.out.println("Test1---执行静态定时任务时间: " + LocalDateTime.now());
        Thread.sleep(2000);
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void test2() throws InterruptedException {
        System.out.println("Test2---执行静态定时任务时间: " + LocalDateTime.now());
        Thread.sleep(2000);
    }
}
/*
	理论上应该每2秒同时执行test1和test2方法,两个定时任务同步执行,应该输出下面这种:
	Test1---执行静态定时任务时间: 2021-08-06T16:51:46.013
    Test2---执行静态定时任务时间: 2021-08-06T16:51:46.026
    Test1---执行静态定时任务时间: 2021-08-06T16:51:48.038
    Test2---执行静态定时任务时间: 2021-08-06T16:51:48.052
    而实际上是输出下面:
    Test1---执行静态定时任务时间: 2021-08-06T16:51:46.013
    Test2---执行静态定时任务时间: 2021-08-06T16:51:48.026
    Test1---执行静态定时任务时间: 2021-08-06T16:51:50.038
    Test2---执行静态定时任务时间: 2021-08-06T16:51:52.052
    Test1---执行静态定时任务时间: 2021-08-06T16:51:54.065
    
    可以看到,定时任务是交替执行的,因为是单线程,同一时间只会执行一个定时任务。
    那么如果实现多个定时任务同时执行呢?请看第三种方式创建的定时任务。
*/

动态:基于接口

动态指的是执行时间不是写死的,而是从数据库中读取到的,当我们修改数据库的时间时,
我们的定时任务不需要重启便可更改定时时间。

  • 导入mysql,mybatis依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.0.4.RELEASE</version>
    </parent>
      
    <dependencies>
        <dependency><!--添加Web依赖 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency><!--添加MySql依赖 -->
             <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency><!--添加Mybatis依赖 配置mybatis的一些初始化的东西-->
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency><!-- 添加mybatis依赖 -->
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
  • 创建数据库,表

    DROP DATABASE IF EXISTS `socks`;
    CREATE DATABASE `socks`;
    USE `SOCKS`;
    DROP TABLE IF EXISTS `cron`;
    CREATE TABLE `cron`  (
      `cron_id` varchar(30) NOT NULL PRIMARY KEY,
      `cron` varchar(30) NOT NULL  
    );
    INSERT INTO `cron` VALUES ('1', '0/5 * * * * ?');
  • 配置数据源之类的就不写了,创建定时任务配置类

    @Configuration      //1.主要用于标记配置类,兼备Component的效果。
    @EnableScheduling   // 2.开启定时任务
    public class DynamicScheduleTask implements SchedulingConfigurer {
    
        @Mapper
        public interface CronMapper {
            @Select("select cron from cron limit 1")
            public String getCron();
        }
    
        @Autowired      //注入mapper
        @SuppressWarnings("all")
        CronMapper cronMapper;
    
        /**
         * 执行定时任务.
         */
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    
            taskRegistrar.addTriggerTask(
                    //1.添加任务内容(Runnable)
                    () -> System.out.println("执行动态定时任务: " + 
    					LocalDateTime.now().toLocalTime()),
                    //2.设置执行周期(Trigger)
                    triggerContext -> {
                        //2.1 从数据库获取执行周期
                        String cron = cronMapper.getCron();
                        //2.2 合法性校验.
                        if (StringUtils.isEmpty(cron)) {
                            // Omitted Code ..
                        }
                        //2.3 返回执行周期(Date)
                        return new CronTrigger(cron).nextExecutionTime(triggerContext);
                    }
            );
        }
    }

    写好定时任务配置类后,启动程序运行即可触发定时任务。

多线程定时任务

基于注解设定多线程定时任务

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   //2.开启定时任务
@EnableAsync
@Async
@ComponentScan(basePackageClasses ={},basePackages = {})  //指定定时任务在哪个包或者哪个类有效
public class StaticScheduleTaskConfig {

    //3.添加定时任务
    @Scheduled(cron = "0/1 * * * * ?")
    //或直接指定时间间隔,例如:5秒
    //@Scheduled(fixedRate=5000)
    public void test1() throws InterruptedException {
        System.out.println("Test1---执行静态定时任务时间: " + LocalDateTime.now());
        Thread.sleep(2000);
    }

    @Scheduled(cron = "0/1 * * * * ?")
    //或直接指定时间间隔,例如:5秒
    //@Scheduled(fixedRate=5000)
    public void test2() throws InterruptedException {
        System.out.println("Test2---执行静态定时任务时间: " + LocalDateTime.now());
        Thread.sleep(2000);
    }
}

可以看到只需添加@EnableAsync@Async注解即可实现多线程运行定时任务,
当然@Async注解也可以标注在方法上,标注在类上表示类中所有定时任务都是多线程运行。
上面的案例,运行结果如下:
    Test1---执行静态定时任务时间: 2021-08-06T17:16:11.001
    Test2---执行静态定时任务时间: 2021-08-06T17:16:11.002
    Test2---执行静态定时任务时间: 2021-08-06T17:16:12.001
    Test1---执行静态定时任务时间: 2021-08-06T17:16:12.001
    Test2---执行静态定时任务时间: 2021-08-06T17:16:13.016
    Test1---执行静态定时任务时间: 2021-08-06T17:16:13.016
可以看到,第一个定时任务和第二个定时任务互不影响。