目录
一、定时任务的理解
二、入门案例
三、Cron表达式
四、Cron实战案例
五、多线程案例
定时任务即系统在特定时间执行一段代码,它的场景应用非常广泛:
定时任务的实现主要有以下几种方式:
创建SpringBoot项目,在启动类开启定时任务。
也就是在启动类上方添加@EnableScheduling注解即可开启定时任务,代码如下:
package com.example.springboottaskdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class SpringboottaskdemoApplication { public static void main(String[] args) { SpringApplication.run(SpringboottaskdemoApplication.class, args); } }
编写定时任务类
@Component public class MyTask { // 定时任务方法,每秒执行一次 @Scheduled(cron="* * * * * *") public void task1() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); System.out.println(sdf.format(new Date())); } }
启动项目,定时任务方法按照配置定时执行。
OK,果然如此,每隔一秒输出当前时间
@Scheduled写在方法上方,指定该方法定时执行。常用参数如下:
OK,先来一个案例,代码如下:任务结束后每隔五秒执行一次
// 立即执行,任务结束后五秒执行一次 @Scheduled(fixedDelay = 5000) public void task1() throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss"); System.out.println("Task1: "+sdf.format(new Date())); }
效果如下:
OK,果然如此,注意这个是任务结束后每隔五秒,如果方法中间加了一个sleep方法,那么执行时间还要加上sleep里面的值,比如说中间加了一个sleep(1000),那么就会每隔6秒执行一次。
fixedRate:任务立即执行,之后每隔多久执行一次,单位是毫秒,上一次任务开始后计算下次执行的时间。
案例如下,代码如下:
// 立即执行,之后每五秒执行一次 @Scheduled(fixedRate = 5000) public void task2() throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss"); // 没有影响五秒输出一次 Thread.sleep(1000); System.out.println("Task2: "+sdf.format(new Date())); }
OK,看如下执行代码确实是不受到sleep影响的
initialDelay:项目启动后不马上执行定时器,根据initialDelay的值延时执行。 为了突出刚刚说的fixedDelay会受到sleep影响,这里配合fixedDelay来结合测试演示一下:
代码如下:
// 项目启动后三秒执行,之后每六秒执行一次 @Scheduled(fixedDelay = 5000,initialDelay = 3000) public void task3() throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss"); // 没有影响五秒输出一次 Thread.sleep(1000); System.out.println("Task3: "+sdf.format(new Date())); }
OK,看运行结果也是隔了三秒才出现第一次打印时间,并且打印时间是隔六秒打印一次
Spring Task依靠Cron表达式配置定时规则。Cron表达式是一个字符串,分为6或7个域,每一个域代表一个含义,以空格隔开。有如下两种语法格式:
- Seconds Minutes Hours DayofMonth Month DayofWeek Year
- Seconds Minutes Hours DayofMonth Month DayofWeek
Seconds(秒):域中可出现 , - * / 四个字符,以及0-59的整数
Minutes(分):域中可出现 , - * / 四个字符,以及0-59的整数
Hours(时):域中可出现 , - * / 四个字符,以及0-23的整数
DayofMonth(日期):域中可出现 , - * / ? L W C 八个字符,以及1-31的整数
注:
Month(月份):域中可出现 , - * / 四个字符,以及1-12的整数或JAN-DEC的单词缩写
DayofWeek(星期):可出现 , - * / ? L # C 八个字符,以及1-7的整数或SUN-SAT 单词缩写,1代表星期天,7代表星期六
Year(年份):域中可出现 , - * / 四个字符,以及1970~2099的整数。该域可以省略,表示每年都触发。
下面有常用的案例,大家可以参考一下
含义 | 表达式 |
---|---|
每隔5分钟触发一次 | 0 0/5 * * * * |
每小时触发一次 | 0 0 * * * * |
每天的7点30分触发 | 0 30 7 * * * |
周一到周五的早上6点30分触发 | 0 30 6 ? * 2-6 |
每月最后一天早上10点触发 | 0 0 10 L * ? |
每月最后一个工作日的18点30分触发 | 0 30 18 LW * ? |
2030年8月每个星期六和星期日早上10点触发 | 0 0 10 ? 8 1,7 2030 |
每天10点、12点、14点触发 | 0 0 10,12,14 * * * |
朝九晚五工作时间内每半小时触发一次 | 0 0 0/30 9-17 ? * 2-6 |
每周三中午12点触发一次 | 0 0 12 ? * 4 |
每天12点触发一次 | 0 0 12 * * * |
每天14点到14:59每分钟触发一次 | 0 * 14 * * * |
每天14点到14:59每5分钟触发一次 | 0 0/5 14 * * * |
每天14点到14:05每分钟触发一次 | 0 0-5 14 * * * |
每月15日上午10:15触发 | 0 15 10 15 * ? |
每月最后一天的上午10:15触发 | 0 15 10 L * ? |
每月的第三个星期五上午10:15触发 | 0 15 10 ? * 6#3 |
好啦,通过这些大家应该就可以领悟了
Spring Task定时器默认是单线程的,如果项目中使用多个定时器,使用一个线程会造成效率低下。
比如说我们设置了两个定时任务,那么因为Spring Task是单线程,如果在第一个定时任务加了一个sleep方法,那么会等第一个方法响应后在执行第二个任务,就很浪费cpu运行时间。代码如下:
@Scheduled(cron = "* * * * * *") public void task1() throws InterruptedException { System.out.println(Thread.currentThread().getId()+"线程执行任务1 - "+new SimpleDateFormat("HH:mm:ss").format(new Date()); Thread.sleep(5000); } @Scheduled(cron = "* * * * * *") public void task2() throws InterruptedException { System.out.println(Thread.currentThread().getId()+"线程执行任务2 - "+new SimpleDateFormat("HH:mm:ss").format(new Date()); }
执行效果如下:可以看到是先执行了任务2,但是他们都要隔五秒才能运行一次,因为通过线程号可以知道这是同一个线程。
因此任务1较浪费时间,会阻塞任务2的运行。此时我们可以给SpringTask配置线程池。代码如下:
package com.example.springboottaskdemo; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executors; @Configuration public class SchedulingConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 创建线程池,设置五个线程 taskRegistrar.setScheduler(Executors.newScheduledThreadPool(4)); } }
这样就不会出现阻塞问题了,因为两个任务不是同一个线程,接下来我们再次运行看看:
执行效果如上,确实不会影响到任务2的运行,但是如果定时任务过多,超过了配置的线程池的线程数量还是会运行错乱。
Ok,SpringBoot到这里就完结撒花了。