互斥锁是java中用redis应对缓存击穿最常用、最稳妥的方案,核心是只让一个线程查库并写缓存,其余线程等待结果;需用redis分布式锁而非synchronized,因后者无法跨jvm生效;关键在于双重检查、锁自动过期、唯一锁值校验释放及finally块确保锁清理。

Java 中用 Redis 应对缓存击穿,互斥锁是最常用、最稳妥的方案。核心不是“谁先来谁查库”,而是“只让一个线程去查,其他人等结果”。关键在于锁的获取、释放时机和重试逻辑,不能漏掉二次检查,也不能让锁一直不释放。
互斥锁的核心流程
请求进来后,按顺序执行以下动作:
- 先查 Redis,命中就直接返回
- 未命中,尝试用 SET key value NX EX seconds 命令加分布式锁(NX 表示仅当 key 不存在才设置成功)
- 加锁成功者:查数据库 → 写入 Redis → 删除锁 key
- 加锁失败者:短暂休眠(如 50ms)→ 重新查缓存 → 若仍没数据,再次尝试加锁(建议限制重试次数,避免无限等待)
为什么必须用 Redis 分布式锁,而不是 synchronized
单机应用可用 synchronized,但生产环境基本是多实例部署。synchronized 只在当前 JVM 内生效,其他服务节点完全感知不到,起不到互斥作用。必须依赖 Redis 的原子命令(如 SETNX 或 Redisson 的 lock())实现跨进程、跨机器的锁控制。
注意:锁 key 要有业务区分度,比如 "shop:123:lock",不能所有请求共用一个锁;锁 value 推荐用唯一 UUID,便于后续校验和安全释放。
关键细节:双重检查 + 锁自动过期
加锁后不能直接查库,要再查一次缓存——因为可能在你排队时,前面的线程已写完缓存。这叫“双重检查”,避免重复查询数据库。
锁本身必须设 TTL(比如 10 秒),防止持有锁的线程异常崩溃导致死锁。但 TTL 不能太短,要大于数据库查询 + 缓存写入的耗时;也不宜过长,否则阻塞时间太久。实际中可结合接口 P99 耗时 + 安全余量设定。
简易 Java 实现要点(Jedis 示例)
使用 Jedis 操作时,关键代码片段如下:
- 生成唯一锁值:String lockValue = UUID.randomUUID().toString();
- 加锁:jedis.set("key:lock", lockValue, "NX", "EX", 10);
- 释放锁前校验:if (lockValue.equals(jedis.get("key:lock"))) { jedis.del("key:lock"); }(避免误删他人锁)
- 务必在 finally 块中释放锁,确保异常时也能清理
更推荐使用 Redisson 客户端,它封装了自动续期、看门狗机制和公平锁等能力,省去手动管理锁生命周期的麻烦。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











