一、我们之前遇到了什么问题?(必须先懂)
我们之前写释放锁的代码是这样的:
java
运行
// 1. 判断锁是不是自己的
String id = stringRedisTemplate.opsForValue().get(key);
if(threadId.equals(id)){
// 2. 是自己的,才删除
stringRedisTemplate.delete(key);
}
这段代码不是原子的!
它分成 2 步:
- 查(判断是不是我的锁)
- 删(释放锁)
高并发下会发生恐怖的事情:
- 线程 A 查询 → 是我的锁
- 刚查完,突然卡住了!
- 锁超时过期 → Redis 自动删了
- 线程 B 抢到锁
- 线程 A 恢复 → 直接删锁!
- 线程 A 把线程 B 的锁删了!
二、为什么会发生?
因为 查询 和 删除 是两步!中间可以被打断!
plaintext
查询 → 卡住 → 锁超时 → 别人上锁 → 你删除 → 删错锁!
三、怎么解决?
必须让 查询 + 删除 变成一步执行,不能被打断!
谁能做到?
Lua 脚本!
四、Lua 脚本怎么实现原子性?
原理超级简单:
Redis 执行 Lua 脚本时,会把整个脚本当成一条命令!
中间不允许任何其他命令插入!
绝对不被打断!
五、黑马点评里的 Lua 脚本(释放锁)
lua
-- 1. 获取锁中的线程标识
local id = redis.call('get', KEYS[1])
-- 2. 比较是否是自己的线程
if id == ARGV[1] then
-- 3. 是自己的,删除锁
return redis.call('del', KEYS[1])
end
-- 4. 不是自己的,直接返回 0
return 0
这一段代码在 Redis 中是 一步执行!
六、Lua 脚本如何保证原子性?(最核心)
在 Redis 中,Lua 脚本是独占执行的!
意思:
- Redis 单线程执行 Lua
- 执行期间,其他命令必须排队!
- 查询 和 删除 绝对连在一起,不可能被打断!
七、用了 Lua 之后,恐怖场景彻底消失!
流程变成:
- 线程 A 执行 Lua 脚本
- 查询 + 删除 一步做完
- 中间不可能卡住、不可能被打断、不可能插入其他命令
所以:
绝对不会出现:刚查完锁,就被别人抢走,然后删错锁的问题!
八、最直白总结(3 句话)
- 原来释放锁是两步(查、删),会被打断 → 出现删错锁的 BUG
- Lua 脚本把两步变成一步 → 原子性,绝对不被打断
- Redis 单线程执行 Lua → 绝对安全
九、一句话记忆
Lua 脚本 = 多条 Redis 命令合成一条,保证原子性,不被打断!
你现在是不是彻底懂了?
整个黑马点评分布式锁的终极方案就是:
- setnx + 过期时间 加锁
- Lua 脚本 释放锁(保证原子性)
- 解决:删错锁问题
全部评论
(0) 回帖