@Scheduled
定时任务的核心。
@Scheduled
默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。
参数解析:
cron
: cron表达式,根据表达式循环执行,与fixedRate
属性不同的是它是将时间进行了切割。(@Scheduled(cron = "0/5 * * * * *"
)任务将在5、10、15、20...
这种情况下进行工作)fixedRate
: 每隔多久执行一次;(@Scheduled(fixedRate = 1000
) 假设第一次工作时间为2018-05-29 16:58:30
,工作时长为3秒,那么下次任务的时候就是2018-05-29 16:58:33
,配置成异步后,只要到了执行时间就会开辟新的线程工作),如果(@Scheduled(fixedRate = 3000
) 假设第一次工作时间为2018-05-29 16:58:30
,工作时长为1秒,那么下次任务的时间依然是2018-05-29 16:58:33
)fixedDelay
: 当前任务执行完毕后等待多久继续下次任务(@Scheduled(fixedDelay = 3000
) 假设第一次任务工作时间为2018-05-29 16:54:33
,工作时长为5秒,那么下次任务的时间就是2018-05-29 16:54:41
)initialDelay
: 第一次执行延迟时间,只是做延迟的设定,与fixedDelay
关系密切,配合使用,相辅相成。@Async
代表该任务可以进行异步工作,由原本的串行改为并行。
单线程案例:
@Slf4j
@Component
@EnableScheduling // 开启定时任务
public class SpringTask {
@Scheduled(cron = "0/1 * * * * *")
public void scheduled2() throws InterruptedException {
Thread.sleep(3000);
log.info("scheduled2 每1秒执行一次:{}", LocalDateTime.now());
}
@Scheduled(fixedRate = 1000)
public void scheduled1() throws InterruptedException {
Thread.sleep(3000);
log.info("scheduled1 每1秒执行一次:{}", LocalDateTime.now());
}
}
多线程案例:
@Slf4j
@Component
@EnableScheduling // 开启定时任务
@EnableAsync // 开启多线程
public class SpringTask {
@Async
@Scheduled(fixedDelay = 1000) //间隔1秒
public void first() throws InterruptedException {
System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}
@Async
@Scheduled(fixedDelay = 2000)
public void second() {
System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
}
}
从控制台可以看出,第一个定时任务和第二个定时任务互不影响;
并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。
Cron表达式参数分别表示:
cron表达式在线生成: http://cron.qqe2.com/
显然,使用@Scheduled 注解很方便,但缺点是当我们调整了执行周期的时候,需要重启应用才能生效,这多少有些不方便。为了达到实时生效的效果,可以使用接口来完成定时任务。
数据库脚本。
CREATE TABLE `cron` (
`cron_id` varchar(30) NOT NULL PRIMARY KEY,
`cron` varchar(30) NOT NULL
);
INSERT INTO `cron` VALUES ('1', '0/5 * * * * ?');
创建定时器。
这里添加的是TriggerTask,目的是循环读取数据库设置好的执行周期,以及执行相关定时任务的内容。
具体代码如下:
@Component
@EnableScheduling // 开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {
@Resource
CronService cronService;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 定时任务要执行的内容
Runnable task = ()->{
System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime());
};
// 设置执行周期
Trigger trigger = triggerContext -> {
List<Cron> crons = cronService.list();
String cron = crons.get(0).getCron();
// 合法性校验.
if (StringUtils.isEmpty(cron)) {
// Omitted Code ..
}
// 返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
};
// 注册一个任务
taskRegistrar.addTriggerTask(task, trigger);
}
}
测试。
打印时间是每5秒一次,
将执行周期修改为每3秒执行一次,
发现执行周期已经改变,并且不需要重启应用。
注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。
一般出现在SpringMVC的项目中,因为有两个上下文,一个是SpringMVC容器,一个Spring容器,
如果你的定时任务类同时被两个容器管理了,就会执行两次。
解决方案是只被一个容器来管理。
cron表达式实际上是由七个子表达式组成。这些表达式之间用空格分隔:
1、Seconds (秒)
2、Minutes(分)
3、Hours(小时)
4、Day-of-Month (天)
5、Month(月)
6、Day-of-Week (周)
7、Year(年)
例: 0 0 12 ? * WED
意思是:每个星期三的中午12点执行。
子表达式范围:
1、Seconds (0~59)
2、Minutes (0~59)
3、Hours (0~23)
4、Day-of-Month (1~31,但是要注意有些月份没有31天)
5、Month (0~11,或者"JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV,DEC")
6、Day-of-Week (1~7,1=SUN 或者"SUN, MON, TUE, WED, THU, FRI, SAT”)
7、Year (1970~2099)
表达式例子:
0 * * * * ?
每1分钟触发一次
0 0 * * * ?
每天每1小时触发一次
0 0 10 * * ?
每天10点触发一次
0 * 14 * * ?
在每天下午2点到下午2:59期间的每1分钟触发
0 30 9 1 * ?
每月1号上午9点半
0 15 10 15 * ?
每月15日上午10:15触发
*/5 * * * * ?
每隔5秒执行一次
0 */1 * * * ?
每隔1分钟执行一次
0 0 5-15 * * ?
每天5-15点整点触发
0 0/3 * * * ?
每三分钟触发一次
0 0-5 14 * * ?
在每天下午2点到下午2:05期间的每1分钟触发
0 0/5 14 * * ?
在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,18 * * ?
在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
0 0/30 9-17 * * ?
朝九晚五工作时间内每半小时
0 0 10,14,16 * * ?
每天上午10点,下午2点,4点
0 0 12 ? * WED
表示每个星期三中午12点
0 0 17 ? * TUES,THUR,SAT
每周二、四、六下午五点
0 10,44 14 ? 3 WED
每年三月的星期三的下午2:10和2:44触发
0 15 10 ? * MON-FRI
周一至周五的上午10:15触发
0 0 23 L * ?
每月最后一天23点执行一次
0 15 10 L * ?
每月最后一日的上午10:15触发
0 15 10 ? * 6L
每月的最后一个星期五上午10:15触发
0 15 10 * * ? 2005
2005年的每天上午10:15触发
0 15 10 ? * 6L 2002-2005
2002年至2005年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3
每月的第三个星期五上午10:15触发