关注牛哥公众号:牛牛码特,回复:1,即可获得秋招大礼包
面经统计表/Java Top100面试题/秋招企业投递表
第一种方式是用String结构:key是锁名称,value前32位存线程标识,后面存次数。线程加锁时先用 GETRANGE 命令截取前32位与自己的 ID 进行比对,一致则取后面次数增1,否则新建锁。解锁时同样截取比对,次数减到0则删除锁。加锁和解锁过程都需要依靠 Lua 脚本来保障原子性。
第二种方式是用Hash结构:key是锁名称,field存线程标识(客户端ID+线程ID),value是重入次数。加锁时,通过Lua 脚本原子判断:若锁不存在则hset初始化次数1并设过期;若存在且field匹配则hincrby增次数,否则失败。解锁时同样用Lua脚本,判断field匹配后减次数,为0则del锁。Redisson RLock 即是通过这种方式实现的。
具体设计为:
加锁逻辑通过 Lua 脚本原子执行,核心是用 GETRANGE 截取线程标识,判断是否与当前线程一致,再更新重入次数:
GETRANGE key 0 31 截取前 32 位线程标识: GETRANGE key 32 -1 获取当前重入次数,+1 后更新 Value,并刷新过期时间;
-- KEYS[1]:锁的key(如lock:order:123)
-- ARGV[1]:当前线程标识(32位固定长度)
-- ARGV[2]:过期时间(秒)
-- 1. 检查锁是否存在
if redis.call('exists', KEYS[1]) == 0 then
-- 2. 不存在,首次加锁(线程标识 + 重入次数1)
redis.call('set', KEYS[1], ARGV[1] .. '1', 'EX', ARGV[2])
return 1 -- 加锁成功
end
-- 3. 锁存在,截取前32位线程标识
local storedThreadId = redis.call('getrange', KEYS[1], 0, 31)
if storedThreadId == ARGV[1] then
-- 4. 线程一致,获取当前重入次数(32位之后的部分)
local currentCount = redis.call('getrange', KEYS[1], 32, -1)
-- 5. 重入次数+1,更新锁并刷新过期时间
redis.call('set', KEYS[1], ARGV[1] .. tostring(tonumber(currentCount) + 1), 'EX', ARGV[2])
return 1 -- 重入成功
else
-- 6. 线程不一致,返回剩余过期时间(秒)
return redis.call('ttl', KEYS[1])
end解锁同样通过 Lua 脚本原子执行,确保只释放当前线程的锁,并正确处理重入次数:
GETRANGE key 0 31 截取线程标识,若与当前线程不一致,返回失败(防止误释放)。GETRANGE key 32 -1 获取当前次数: -- KEYS[1]:锁的key(如lock:order:123)
-- ARGV[1]:当前线程标识(32位固定长度)
-- ARGV[2]:过期时间(秒,用于更新次数时刷新)
-- 1. 检查锁是否存在
if redis.call('exists', KEYS[1]) == 0 then
return 1 -- 锁不存在,释放成功
end
-- 2. 截取存储的线程标识
local storedThreadId = redis.call('getrange', KEYS[1], 0, 31)
if storedThreadId ~= ARGV[1] then
return 0 -- 线程不一致,释放失败
end
-- 3. 获取当前重入次数
local currentCount = tonumber(redis.call('getrange', KEYS[1], 32, -1))
local newCount = currentCount - 1
if newCount > 0 then
-- 4. 次数仍>0,更新次数并刷新过期时间
redis.call('set', KEYS[1], ARGV[1] .. tostring(newCount), 'EX', ARGV[2])
else
-- 5. 次数=0,彻底删除锁
redis.call('del', KEYS[1])
end
return 1 -- 释放成功Redisson 的可重入分布式锁基于 Redis 的 Hash 数据结构 存储,具体设计为:

加锁流程:
-- KEYS[1]:锁的key(如myLock)
-- ARGV[1]:过期时间(毫秒,默认30000)
-- ARGV[2]:当前线程标识(如{192.168.1.100:6379}:123)
-- 1. 检查锁是否存在
if (redis.call('exists', KEYS[1]) == 0) then
-- 2. 不存在,创建锁(hset设置线程标识和重入次数1)
redis.call('hset', KEYS[1], ARGV[2], 1)
-- 3. 设置过期时间
redis.call('pexpire', KEYS[1], ARGV[1])
return nil -- 加锁成功
end
-- 4. 锁已存在,检查是否当前线程持有
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
-- 5. 是当前线程,重入次数+1
redis.call('hincrby', KEYS[1], ARGV[2], 1)
-- 6. 刷新过期时间
redis.call('pexpire', KEYS[1], ARGV[1])
return nil -- 重入成功
end
-- 7. 其他线程持有锁,返回剩余过期时间
return redis.call('pttl', KEYS[1])解锁操作同样通过 Lua 脚本原子执行,确保只释放当前线程持有的锁,且正确处理重入次数:
-- KEYS[1]:锁的key(如myLock)
-- ARGV[1]:当前线程标识(如{192.168.1.100:6379}:123)
-- 1. 检查是否当前线程持有锁
if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
return nil -- 不是当前线程的锁,直接返回
end
-- 2. 重入次数-1
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)
-- 3. 若次数仍>0,不释放锁
if (counter > 0) then
return 0
else
-- 4. 次数=0,彻底删除锁
redis.call('del', KEYS[1])
return 1 -- 释放成功
end