Redis可重入锁的实现设计

  • 锁实现

  • 锁使用

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

设计两个线程调⽤ 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个参数,锁的key

local 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

– 锁的 key

local 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);

– 判断重入次数是否已为0

if (count == 0) then

– 等于 0,说明可以释放锁,直接删除

redis.call(‘del’, key);

return nil;

end;

在项目中集成

=====================================================================

编写 RedisLock 类


@Getter

@Setter

public 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”)));

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值