
Spring Boot 的 @Scheduled 注解中,*/4 在秒/分字段无效,需将间隔写在「小时」字段并使用 0/4 格式,才能实现每 4 小时触发一次(如 00:00、04:00、08:00…)。
spring boot 的 `@scheduled` 注解中,`*/4` 在秒/分字段无效,需将间隔写在「小时」字段并使用 `0/4` 格式,才能实现每 4 小时触发一次(如 00:00、04:00、08:00…)。
在 Spring Boot 中使用 @Scheduled(cron = "...") 实现定时任务时,Cron 表达式的语义与 Unix/Linux crontab 并不完全一致——Spring 默认采用 6 位 Quartz 风格格式(秒、分、时、日、月、周),而非常见的 5 位 POSIX 格式。这是导致许多开发者误配的关键原因。
你原写的表达式:
@Scheduled(cron = "0 0 */4 * * *")
看似合理(“每 4 小时”),但 */4 出现在分钟字段(第 3 位),实际含义是「每小时的第 0、4、8、12、16、20、24、28、32、36、40、44、48、52、56 分钟各执行一次」,即每小时执行 15 次,并非每 4 小时执行一次。而你观察到“只运行了一次”,极可能是因为任务被 @SchedulerLock 成功抢占后,后续调度因锁未释放、线程阻塞、或应用未启用定时支持(如缺少 @EnableScheduling)等原因中断,但根本诱因仍是 Cron 表达式语义错误。
✅ 正确写法应将步长 0/4 放在小时字段(第 3 位),表示「从 0 点开始,每隔 4 小时执行一次」:
@Scheduled(cron = "0 0 0/4 * * *")
@SchedulerLock(name = "InsightPhotoUploaderCronJob", lockAtMostFor = PT5M, lockAtLeastFor = PT5M)
public void uploadInsightPhotos() {
// 业务逻辑
}该表达式严格对应 6 位 Quartz Cron 含义: | 位置 | 字段 | 取值范围 | 说明 | |------|--------|--------------|--------------------------| | 1 | 秒 | 0–59 | 0 → 每分钟第 0 秒触发 | | 2 | 分 | 0–59 | 0 → 每小时第 0 分 | | 3 | 小时 | 0–23 | 0/4 → 从 0 点起,每 4 小时(0, 4, 8, 12, 16, 20) | | 4 | 日 | 1–31 | * → 每天 | | 5 | 月 | 1–12 | * → 每月 | | 6 | 周 | 1–7(1=周日)| * → 每周 |
? 补充验证:
- 0 */10 * * * * ✅ 有效:每 10 分钟执行(分字段 */10 → 0,10,20,30,40,50);
- 0 0 */4 * * * ❌ 错误:*/4 在分字段无意义(分钟最大 59,*/4 会生成 0,4,8,...,56,共 15 次/小时);
- 0 0 0/4 * * * ✅ 正确:小时字段 0/4 明确表示 0,4,8,12,16,20 点整执行。
⚠️ 注意事项:
- 必须在启动类或配置类上添加 @EnableScheduling,否则 @Scheduled 不生效;
- @SchedulerLock(来自 spring-boot-starter-quartz 或 shedlock)依赖外部存储(如数据库、Redis)协调分布式锁,确保单实例执行——请确认锁存储已正确配置且健康;
- 若使用 ShedLock,请确保 TaskScheduler Bean 已注册,并启用 @EnableScheduling 和 @EnableSchedulerLock(ShedLock v5+);
- 开发环境建议搭配 logging.level.org.springframework.scheduling=DEBUG 查看调度日志,确认触发时间是否符合预期。
总结:Cron 表达式必须严格匹配 Quartz 6 位语义,时间粒度控制应落在对应字段(小时任务调小时位,非分钟位)。调试时优先验证基础表达式(如 0 0 * * * * 每小时)、再叠加步长,可大幅降低配置失误率。

















