Cron 表达式详解
Cron 表达式是用于定义定时任务执行时间的字符串,广泛应用于 Spring 的 @Scheduled、Quartz 等定时任务框架。其核心是通过 时间字段 和 通配符 组合实现复杂的调度规则。
1. 表达式格式
Cron 表达式由 6或7个字段 组成,分别表示不同时间单位(Spring 中通常用 6位 格式)。格式如下:
字段 | 允许值 | 特殊字符 | 说明 |
---|---|---|---|
秒(Seconds) | 0-59 | , - * / | 可精确到秒级调度 |
分(Minutes) | 0-59 | , - * / | |
小时(Hours) | 0-23 | , - * / | |
日(Day) | 1-31 | , - * / ? L W | 月份中的某一天 |
月(Month) | 1-12 或 JAN-DEC | , - * / | |
周(Week) | 0-7 或 SUN-SAT (0=周日) | , - * / ? L # | 周几(1=MON, 7=SUN) |
年(Year) | 1970-2099 (可选) | , - * / | Spring 中通常省略 |
2. 特殊字符解析
符号 | 作用 | 示例 |
---|---|---|
* | 匹配任意值 | 0 * * * * ? 每分钟的0秒执行 |
? | 仅在 日 或 周 字段使用,表示“无意义” | 0 0 0 * * ? 每天0点执行 |
- | 范围区间 | 0 0 10-12 * * ? 10-12点每小时执行 |
, | 多个值 | 0 0 2,14 * * ? 每天2点和14点执行 |
/ | 步长(间隔时间) | 0 0/5 * * * ? 每隔5分钟执行 |
L | 最后一天(仅 日 或 周 字段) | 0 0 L * * ? 每月最后一天0点执行 |
W | 最近工作日(仅 日 字段) | 0 0 0 15W * ? 每月15日最近的工作日执行 |
# | 指定月份的周几(仅 周 字段) | 0 0 0 ? * 6#3 每月第3个周五执行 |
3. 常用示例
表达式 | 说明 |
---|---|
0 0 12 * * ? | 每天中午12点执行 |
0 0/5 14 * * ? | 每天下午2点开始,每隔5分钟执行一次 |
0 15 10 ? * MON-FRI | 每周一至周五上午10:15执行 |
0 0 0 1 1 ? 2024 | 2024年1月1日0点执行(需7位表达式) |
0 0 8-18/2 ? * MON | 每周一上午8点到下午6点,每隔2小时执行一次 |
0 0 0 L * ? | 每月最后一天的0点执行 |
0 0 0 15W * ? | 每月15日最近的工作日执行 |
0 0 0 ? * 6#3 | 每月第3个周五0点执行 |
4. 重点规则
1. 日与周的互斥性
- 若同时指定 日 和 周,需用 ? 忽略其中一个字段。
- ✅ 正确:0 0 0 ? * MON(每周一执行,忽略日)
- ❌ 错误:0 0 0 * * MON(日和周同时生效,可能冲突)
2. 月份和星期的缩写
- 月份:JAN, FEB, MAR… DEC
- 星期:SUN, MON, TUE… SAT
- L 和 W 的组合
- LW 表示当月的最后一个工作日。
- L-3 表示倒数第3天。
- 年份字段(可选)
- Spring 的 @Scheduled 不支持年份字段,需用6位表达式。
5. 动态与复杂场景
1. 动态 Cron 表达式
- Spring 中可通过 @Scheduled(cron = “${cron.expression}”) 从配置文件读取。
- 结合数据库动态更新任务:
@Scheduled(cron = "#{@cronService.getCronExpression()}")
public void dynamicTask() {// 业务逻辑
}
2. 避开整点任务高峰
- 添加随机延迟(避免多个任务同时触发):
@Scheduled(cron = "0 #{T(java.util.concurrent.ThreadLocalRandom).current().nextInt(55)} * * * ?")
public void randomMinuteTask() {// 每小时随机分钟执行
}
3. 闰年处理
- Cron 无法直接处理闰年,需结合代码逻辑判断。
6. 调试与验证
1. 在线工具
- Crontab Guru:快速验证表达式。
- CronMaker:生成表达式并查看下次执行时间。
- 日志调试
- 在任务方法中添加日志,观察触发时间是否符合预期:
@Scheduled(cron = "0 0/5 * * * ?")
public void logTask() {log.info("任务执行时间: {}", LocalDateTime.now());
}
7. 常见问题
1. 为什么任务没有执行?
- 检查是否添加
@EnableScheduling
。 - 检查 Cron 表达式是否正确(如 Spring 不支持年份字段)。
- 检查时区设置(默认使用服务器时区,可通过 zone 属性修改)。
2. 如何实现每隔 N 天执行?
方案1
:使用0 0 0 */N * ?
(如0 0 0 */5 * ?
每隔5天执行)。方案2
:通过代码记录上一次执行时间。
3. 分布式环境下的幂等性
使用 Redis 分布式锁:
@Scheduled(cron = "0 0 * * * ?")
public void distributedTask() {if (redisLock.tryLock("taskLock", 10)) {try {// 业务逻辑} finally {redisLock.unlock("taskLock");}}
}
总结
Cron 表达式通过简洁的语法实现了灵活的定时规则,但需注意 字段互斥性 和 特殊字符的适用场景。在复杂业务中,可结合动态配置、分布式锁和日志监控来确保任务稳定执行。