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 表达式通过简洁的语法实现了灵活的定时规则,但需注意 字段互斥性 和 特殊字符的适用场景。
在复杂业务中,可结合动态配置、分布式锁和日志监控来确保任务稳定执行。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论