Java用Redis实现定时任务防重调度的核心是:任务启动前用SET NX PX原子抢锁,value为UUID+线程ID,释放时用Lua脚本校验身份后删除,配合@Scheduled和try-finally确保锁正确获取与释放。

Java 用 Redis 实现定时任务的防重调度,核心是“每个任务启动前先抢锁,抢到才执行,执行完立刻释放”,避免集群中多个实例同时跑同一份定时逻辑。
锁的获取必须原子且带过期时间
不能分两步做(先判断再 set),要用 Redis 的 SET key value NX PX timeout 命令一次性完成。其中:
- NX 确保只在锁不存在时设置成功,天然互斥
- PX 指定毫秒级过期时间(比如 30000),防止任务卡住或节点宕机导致锁永久占用
- value 必须是全局唯一标识(推荐 UUID + 当前线程 ID),后续释放锁时用来校验身份
释放锁必须校验持有者,不能无脑 del
直接 DEL key 会误删别人持有的锁。正确做法是用 Lua 脚本保证“读值—比对—删除”三步原子执行:
Spring Data Redis 中可通过 RedisTemplate.execute() 执行该脚本,返回 1 表示释放成功。
立即学习“Java免费学习笔记(深入)”;
定时任务需与分布式锁配合编码
以 Spring @Scheduled 为例,不直接执行业务,而是封装成带锁逻辑的方法:
- 定义一个锁工具类(如
RedisDistributedLock),提供tryLock()和unlock() - @Scheduled 方法内调用
tryLock(),返回 true 才继续执行业务逻辑 - 务必用 try-finally 包裹,确保无论成功失败都执行
unlock() - 若任务可能超时(比如报表生成偶尔要 45 秒),锁过期时间得设得比最大预期耗时更长,或引入看门狗机制自动续期
实际部署要注意几个细节
光写对代码还不够,生产环境容易踩坑:
- Redis 连接必须指向同一个实例或高可用集群(如 Redis Cluster 或哨兵模式),不能各连各的主节点
- 锁 Key 命名要有业务含义和可维护性,例如
lock:report:daily_summary,避免硬编码 - 不要依赖本地时间判断锁是否过期——所有逻辑交给 Redis 自身的过期机制
- 如果用 Spring Integration 的
RedisLockRegistry,注意它默认不支持跨 JVM 的可重入,简单场景够用,复杂调度建议手写控制


















