SpringBoot 集成了 @Scheduled 的相关依赖 (org.springframework.scheduling.annotation.Scheduled); 我们只需要直接使用即可.
@Scheduled 注解的使用步骤:
第一步: 在启动类上面启用定时任务
第二步: 在要定时执行的方法上面, 加上 @Scheduled 注解, 并指定执行间隔
第三步: 把 @Scheduled 所在的类注入到容器中
第四步: 启动启动类 (注: 启动启动类之后, 定时任务就开始了)
注意: Spring 的定时任务默认是单线程的. 如果有多个定时任务, 那么执行起来时间会有问题; 这时就需要开启多线程了 (先把基本知识介绍完, 本文末尾再介绍线程问题).
注: 如果只有一个定时任务, 那么我们可以将这个定时任务单独拿出来作为一个微服务; 这样一来就不需要开启多线程了.
@Scheduled 的定时策略:
cronExpression 定义时间规则, cron 表达式由 6 或 7 个空格分隔的时间字段组成: 秒 分 时 日 月 星期 年 (可选);
注: 如果 Spring 中使用的是 Quartz 等的话, 那么 cron 表达式是支持七位的; 如果 Spring 中使用的是 Spring Task 等的话,
那么 cron 表达式只支持六位.
字段
允许值
允许的特殊字符
秒
0-59
, - */
分
0-59
, - */
时
0-23
, - */
日
- 1-31
- , - * ? / L W C
月
1-12 或 JAN-DEC
, - */
星期
1-7 或 SUN-SAT
注: 1 是星期天, 7 是星期六
, - * ? / L C #
年
1970-2099
, - */
特殊字符:
/ : 指定每隔多久执行一次 如: 0/5 * * * * ? : 每 5 秒执行一次
注: 此时 / 的 "分子" 位置的值为: 定义此次计划执行时间允许值的最小值, 如:
34/10 * * * * ? : 表示此次计划只在 34,44,54 秒时执行, 在 4,14,24 秒时不会执行.
注: */10 等价于 0/10
* : 通配, 每个单位时间都要执行 如:"*" 在分钟的字段域里表示 "每分钟"
? : 只在日期域或者星期域中使用 使用场景示例: 月份中的日期和星期中的日期这两个元素时互斥的时候应该通过设置一个问号来表明不想设置那个字段.
- : 用来指定一个范围, 在这个时间范围内, 就要执行.
如: 10-12 在小时域意味着 10 点, 11 点, 12 点, 那么在这个时间域内要执行 Scheduled 计划
, : 用来指定多个时间值 (一般用于不相邻的时间)
如: 0,13,45 * * * * ? 表示 在第 0 秒, 13 秒, 45 秒时要执行 Scheduled 计划.
# : 只允许在星期域中出现.
如: 6#3 表示本月第三周的星期五 (6 表示星期五, 3 表示第三周).
L : last 的省略写法
如果出现在日域, 那么表示一个月的最后一天
如: 0 * * L 3 ? 表示 三月的最后一天, 每小时每分钟都要执行一次
如果出现在星期域, 那么表示一个月的最后一个星期几
如: 0 * * * 3 7L 表示 三月的最后一个星期六 (注: 7 表示星期六), 每小时每分钟都要执行一次
W : 指向该日, 如果该日不是工作日, 那么指向本月里离得最近的一个工作日.
0 * * 15W * ? 表示每个月的 15 号为工作日 (注: 如果 15 号为节假日的话, 那么指向离它最近的一个本月内的工作日).
C : 允许在日期域和星期域出现.
示例:
"0 0 10,14,16 * * ?"
每天上午 10 点, 下午 2 点, 4 点
"0 0/30 9-17 * * ?"
朝九晚五工作时间内每半小时
"0 0 12 ? * WED"
表示每个星期三中午 12 点
"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 期间的每 1 分钟触发
"0 0/5 14 * * ?"
在每天下午 2 点到下午 2:55 期间的每 5 分钟触发
"0 0/5 14,18 * * ?"
在每天下午 2 点到 2:55 期间和下午 6 点到 6:55 期间的每 5 分钟触发
"0 0-5 14 * * ?"
在每天下午 2 点到下午 2:05 期间的每 1 分钟触发
"0 10,44 14 ? 3 WED"
每年三月的星期三的下午 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"
每月的第三个星期五上午 10:15 触发
线程问题:
在同一个线程中, 如果执行某一个定时任务所需时间较长, 那么就可能影响其他所需时间较短的定时任务.
即: 当所需时间较长的方法抢占到了线程; 在其还没有运行完时, 其他的定时任务错过了 "定时".
如:
控制台打印出的结果为:
使用多线程解决此问题:
配置线程池 (SpringBoot 配置线程池无需引入其他依赖, 直接使用注解即可):
给出文字版:
- // 线程池配置
- @Configuration
- @EnableAsync
- public class ThreadPoolConfig {
- @Bean
- public Executor asyncExecutor() {
- ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
- // 配置核心线程数
- executor.setCorePoolSize(5);
- // 配置最大线程数
- executor.setMaxPoolSize(5);
- // 配置队列大小
- executor.setQueueCapacity(99999);
- // 配置线程池中的线程的名称前缀
- executor.setThreadNamePrefix("async-executor-");
- // rejection-policy: 当 pool 已经达到 max size 的时候, 如何处理新任务
- // CALLER_RUNS: 不在新线程中执行任务, 而是由调用者所在的线程来执行
- executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
- // 执行初始化
- executor.initialize();
- return executor;
- }
- }
在有 @Scheduled 定时计划的方法上, 使用线程池:
此时, 控制台打印出的信息为:
如上图所示: 方法 Two 每 5 秒执行一次; 方法 One 每 10 秒执行一次 (截图我只截了一点, 下面的也遵循这个规律)
由此可见: 开启多线程使用线程池后, 解决了之前的问题.
来源: http://www.bubuko.com/infodetail-2944655.html