- 锁实现
- 锁使用
可重⼊意味着线程可进⼊它已经拥有的锁的同步代码块。
设计两个线程调⽤ 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 脚本。
=====================================================================
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;
=====================================================================
@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开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!