千家信息网

Redis如何实现可重入锁的设计

发表于:2025-11-08 作者:千家信息网编辑
千家信息网最后更新 2025年11月08日,这篇文章主要介绍Redis如何实现可重入锁的设计,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!但是仍然有些场景是不满⾜的,例如⼀ 个⽅法获取到锁之后,可能在⽅法内调这个⽅法此时
千家信息网最后更新 2025年11月08日Redis如何实现可重入锁的设计

这篇文章主要介绍Redis如何实现可重入锁的设计,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

但是仍然有些场景是不满⾜的,例如⼀ 个⽅法获取到锁之后,可能在⽅法内调这个⽅法此时就获取不到锁了。这个时候我们就需要把锁改进成可 重⼊锁了。 重⼊锁,指的是以线程为单位,当⼀个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,⽽其 他的线程是不可以的。可重⼊锁的意义在于防⽌死锁。 实现原理是通过为每个锁关联⼀个请求计数器和⼀个占有它的线程。当计数为 0 时,认为锁是未被占有 的;线程请求⼀个未被占有的锁时,JVM 将记录锁的占有者,并且将请求计数器置为 1 。 如果同⼀个线程再次请求这个锁,计数将递增;每次占⽤线程退出同步块,计数器值将递减。直到计数器 为 0, 锁被释放。 关于⽗类和⼦类的锁的重⼊:⼦类覆写了⽗类的 synchonized ⽅法,然后调⽤⽗类中的⽅法,此时如果没有重⼊的锁,那么这段代码将产⽣死锁。

代码演示

不可重⼊

  • 不可重⼊锁

  • 使用不可重入锁
    当前线程执⾏ call() ⽅法⾸先获取 lock,接下来执⾏ inc() ⽅法就⽆法执⾏ inc() 中的逻辑,必须先释放锁。该例很好的说明了不可重⼊锁。

可重入锁

  • 锁实现

  • 锁使用

可重⼊意味着线程可进⼊它已经拥有的锁的同步代码块。

设计两个线程调⽤ call() ⽅法,第⼀个线程调⽤ call() ⽅法获取锁,进⼊ lock() ⽅法,由于初始 lockedBy 是 null,所以不会进⼊ while ⽽挂起当前线程,⽽是增量 lockedCount 并记录 lockBy 为第 ⼀个线程。

接着第⼀个线程进⼊ inc() ⽅法,由于同⼀进程,所以不会进⼊ while ⽽挂起,接着增量 lockedCount,当第⼆个线程尝试 lock,由于 isLocked=true, 所以他不会获取该锁,直到第⼀个线程调⽤两次 unlock() 将 lockCount 递减为 0,才将标记为 isLocked 设置为 false。

设计思路

假设锁的key为"lock",hashKey是当前线程的id:"threadId",锁自动释放时间假设为20。

获取锁

判断lock是否存在 EXISTS lock

  • 不存在,则自己获取锁,记录重入层数为1.

  • 存在,说明有人获取锁了,继续判断是不是自己的锁,即判断当前线程id作为hashKey是否存在:HEXISTS lock threadId

    • 不存在,说明锁已经有了,且不是自己获取的,锁获取失败.

    • 存在,说明是自己获取的锁,重入次数+1: HINCRBY lock threadId 1 ,最后更新锁自动释放时间, EXPIRE lock 20

释放锁

判断当前线程id作为hashKey是否存在: HEXISTS lock threadId

  • 不存在,说明锁已失效

  • 存在,说明锁还在,重入次数减1: HINCRBY lock threadId -1 ,

    • 获取新的重入次数,判断重入次数是否为0,为0说明锁全部释放,删除key: DEL lock

因此,存储在锁中的信息就必须包含:key、线程标识、重入次数。不能再使用简单的 key-value 结构, 这里推荐使用 hash 结构。而且要让所有指令都在同一个线程中操作,那么使用 lua 脚本。

lua 脚本

lock.lua

local key = KEYS[1]; -- 第1个参数,锁的keylocal threadId = ARGV[1]; -- 第2个参数,线程唯一标识local releaseTime = ARGV[2]; -- 第3个参数,锁的自动释放时间if(redis.call('exists', key) == 0) then -- 判断锁是否已存在    redis.call('hset', key, threadId, '1'); -- 不存在, 则获取锁    redis.call('expire', key, releaseTime); -- 设置有效期    return 1; -- 返回结果end;if(redis.call('hexists', key, threadId) == 1) then -- 锁已经存在,判断threadId是否是自己        redis.call('hincrby', key, threadId, '1'); -- 如果是自己,则重入次数+1    redis.call('expire', key, releaseTime); -- 设置有效期    return 1; -- 返回结果end;return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败

unlock.lua

-- 锁的 keylocal key = KEYS[1];-- 线程唯一标识local threadId = ARGV[1];-- 判断当前锁是否还是被自己持有if (redis.call('hexists', key, threadId) == 0) then--     如果已经不是自己,则直接返回    return nil;end;-- 是自己的锁,则重入次数减一local count = redis.call('hincrby', key, threadId, -1);-- 判断重入次数是否已为0if (count == 0) then--     等于 0,说明可以释放锁,直接删除    redis.call('del', key);    return nil;end;

在项目中集成

编写 RedisLock 类

@Getter@Setterpublic class RedisLock {    private RedisTemplate redisTemplate;    private DefaultRedisScript lockScript;    private DefaultRedisScript unlockScript;    public RedisLock(RedisTemplate redisTemplate) {        this.redisTemplate = redisTemplate;        // 加载释放锁的脚本        this.lockScript = new DefaultRedisScript<>();        this.lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lock.lua")));        this.lockScript.setResultType(Long.class);        // 加载释放锁的脚本        this.unlockScript = new DefaultRedisScript<>();        this.unlockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua")));    }    /**     * 获取锁     * @param lockName 锁名称     * @param releaseTime 超时时间(单位:秒)     * @return key 解锁标识     */    public String tryLock(String lockName, long releaseTime) {        // 存入的线程信息的前缀,防止与其它JVM中线程信息冲突        String key = UUID.randomUUID().toString();        // 执行脚本        Long result = (Long)redisTemplate.execute(                lockScript,                Collections.singletonList(lockName),                key + Thread.currentThread().getId(), releaseTime);        // 判断结果        if(result != null && result.intValue() == 1) {            return key;        }else {            return null;        }    }    /**     * 释放锁     * @param lockName 锁名称     * @param key 解锁标识     */    public void unlock(String lockName, String key) {        // 执行脚本        redisTemplate.execute(                unlockScript,                Collections.singletonList(lockName),                key + Thread.currentThread().getId(), null);    }}

以上是"Redis如何实现可重入锁的设计"这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注行业资讯频道!

线程 次数 脚本 标识 设计 代码 时间 计数器 信息 参数 结果 有效 内容 再次 单位 名称 增量 对象 有效期 篇文章 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 网络技术三级考试好过吗 聊城软件开发工作多少钱 网络技术得去上海百首网络 数据库三层模式和二级映像结构 查看无线网络技术 计算机网络技术基础试卷答案 java 写文件到服务器 广电网络技术维护专业 网络技术专业考一建 网络安全加解密实验 进口网络技术与设备公司 关于停车位的软件开发资金 郑州网络技术培训学校哪家好 乐亭县网络安全培训会议 新闻内容怎么存储到数据库中 将自己的电脑配置成web服务器 游戏软件开发需要什么证书 如何登录cache数据库 服务器千兆带宽 网络安全的主要原理 军人手机网络安全发言稿 徐汇区即时网络技术内容 智帮互联网科技公司官网 网络安全法经过了 网络安全属于什么 服务器 管理口 连接不上 公司网络安全宣传视频创意短片 进公司服务器一定要公司网络 我的世界服务器gta 浙江网络技术服务厂家报价
0