用Redis搞定Java锁那些事儿,实操经验分享和坑点提醒
- 问答
- 2026-01-19 10:14:24
- 3
行,那咱们就直接开整,聊一聊怎么用Redis在Java里实现锁,还有我趟过的一些坑,这东西听起来高大上,其实说白了,就是为了让多个程序或者多个线程在抢同一个资源的时候,能有个先来后到,别打起来。
最基础的玩法:SETNX + DEL(以及为啥它有点“菜”)
最早大家是怎么想的呢?Redis不是有个命令叫SETNX吗?意思是“SET if Not eXists”,如果key不存在,它就设置成功,返回1;如果key已经存在,设置失败,返回0。
这不就是天然的锁吗?我来抢锁,就用一个唯一的key(比如lock:order_123)去执行SETNX,成功了(返回1),好,这个锁就归我了,我去干我的核心业务,干完了,我再把这个key用DEL命令删掉,锁就释放了,其他人就能来抢了。
听起来完美,对吧?但这里立马就有两个大坑等着你:
- 死锁。 万一我的程序在执行业务代码的时候,突然崩溃了!那
DEL命令永远都没机会执行,这个锁就永远不被释放了,其他所有进程都只能干瞪眼,系统就“死”了。 - 误删别人的锁。 假设我抢锁成功,设置了一个30秒的超时时间(这个后面会讲),但我业务逻辑执行了40秒,锁因为超时自动释放了,此时进程B成功抢到了锁,然后我原来的进程A傻乎乎地执行完了,还去执行
DEL,结果把进程B刚创建的锁给删了!这下就乱套了。
这个原始的方法基本不能用在实际项目里,太脆弱了。

进化版:SETNX + EXPIRE + Lua脚本(靠谱了不少)
为了解决死锁问题,我们得给锁加个“保质期”,就是不用等程序崩溃,超过一定时间,锁自动失效,Redis的EXPIRE命令可以给key设置一个存活时间。
于是流程变成了:SETNX lock_key unique_value,成功后再EXPIRE lock_key 10(设置10秒后过期)。
但这样还不是原子操作!万一SETNX成功之后,在执行EXPIRE之前,程序又崩溃了,死锁问题依然存在。
Redis 2.6.12之后,我们有了一个更好的命令:SET key value NX PX milliseconds,这个命令是原子性的,一步到位,同时完成“设置键值”、“仅当不存在时设置”和“设置超时时间”三个操作,这才是正确的姿势。

误删别人锁的问题怎么解决?这就要在value上做文章了,我们不能把value设成简单的1或者"lock",而应该设成一个唯一的值,比如UUID或者请求ID,在删除锁的时候,要判断一下这个value是不是自己当初设置的那个。
GET和DEL是两个命令,不是原子的,万一我GET完发现value匹配,正准备DEL的时候,锁过期了,另一个客户端设置了新值,我又可能误删。
那怎么办?用Lua脚本!因为Lua脚本在Redis中是原子执行的,执行过程中不会被其他命令打断,脚本大概长这样:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
这个脚本的意思是:如果传入的value(ARGV[1])和Redis里存的value一样,我才删除这个锁;否则,返回0表示删除失败。
Redisson:专业的事交给专业的家伙

上面这一套组合拳打下来,一个基本的分布式锁算是实现了,它还不够完善。
- 超时时间设置多久? 设短了,业务没完锁就没了,会导致多个客户端同时进入临界区,设长了,万一客户端真挂了,其他客户端要等很久才能获取锁。
- 获取锁失败怎么办? 一直重试?还是直接失败?
这时候,就该请出大神级的客户端——Redisson了,它是一个在Redis基础上实现的Java驻内存数据网格,对分布式锁有非常成熟和稳健的实现。
Redisson提供的RLock对象,用起来非常简单:
RLock lock = redisson.getLock("myLock");
lock.lock();
try {
// 在这里处理业务
} finally {
lock.unlock();
}
你看,就像用Java本身的锁一样简单,那Redisson背后做了哪些了不起的事情呢?
- 可重入性: 同一个线程可以多次获取同一把锁,计数器会增加,释放时计数器减少,到0才真正释放,这在递归调用时非常有用。
- 看门狗机制: 这是解决锁超时问题的精髓,如果你没有指定超时时间,Redisson会启动一个“看门狗”(watchdog),它会在你获取锁成功后,每隔一段时间(默认是锁过期时间的1/3)去延长锁的过期时间,只要你的应用还活着,业务没执行完,锁就不会因为超时而释放,等你主动解锁时,它会关掉看门狗,这完美解决了业务执行时间不确定的问题。
- 多种锁获取方式: 比如
tryLock()方法,可以指定等待时间和锁的超时时间,获取不到就返回false,避免无限阻塞。 - RedLock算法: 在Redis主从架构下,如果主节点宕机,但锁还没来得及同步到从节点,从节点升级为主节点后,锁信息就丢失了,可能导致多个客户端同时获得锁,Redisson实现了RedLock算法,让你同时向多个独立的Redis实例申请锁,只有当大多数实例都成功时,才算获取锁成功,这大大提高了可靠性(不过这个算法有争议,且性能有损耗,一般场景用主从+哨兵模式下的普通锁也够了)。
最后的坑点提醒
- 网络问题: 分布式锁严重依赖网络和Redis的可用性,如果网络延迟很高,或者Redis响应慢,会影响锁的获取和释放效率,甚至产生意想不到的结果,一定要保证Redis集群的高可用。
- 业务幂等性: 用了分布式锁不代表万事大吉,要时刻想着,万一锁真的失效了(比如Redis某个主节点宕机导致锁丢失),你的业务逻辑是否能够承受得住(比如通过数据库唯一键等手段保证数据最终一致)?让锁成为一个性能优化和协调手段,而不是强依赖。
- 不要滥用锁: 分布式锁是有性能成本的,能不用就不用,优先考虑用消息队列做串行化,或者用数据库的悲观锁、乐观锁,只有在这些方案不合适的时候,再考虑分布式锁。
自己用SET NX PX+Lua脚本可以实现一个简易的分布式锁,但对于严肃的生产环境,强烈推荐直接使用Redisson这样的成熟框架,它能帮你处理掉绝大多数细节和坑,让你更专注于业务本身。
本文由帖慧艳于2026-01-19发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/83605.html
