携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
1. 加锁
使用 setnx
命令实现加锁
eg:订单问题--多次点击创建问题
加锁伪代码
// TODO key类似订单id,1定位为某一种状态
long status = redisService.setnx(key, "1");
复制代码
当线程执行成功后
- 返回1:说明key原本不存在,该线程成功获得锁
- 返回0:说明key存在,线程抢锁失败
2. 解锁
当得到锁的线程执行完任务后需要解锁 del
释放锁,使其他线程能够进入调用
释放锁伪代码
redisService.del(key);
复制代码
释放锁后其他线程可以继续使用 setnx
来获取锁
3. 锁超时
线程在执行过程中出现异常挂掉,并没有释放相应的锁,如果不及时释放,资源将出现死锁,所以在加锁后需要给锁设置相应的超时时间,需要在一定时间后自动释放锁, setnx
没有设置超时方法,需要用到 expire
方法
锁超时时间伪代码
// 30分钟超时时间
redisService.expire(key, 30);
复制代码
加锁综合使用伪代码
long status = redisService.setnx(key, "1");
if(status == 1) {
// 首次加锁,设置锁超时时间30分
redisService.expire(key, 30);
try {
// TODO 业务操作代码
} catch(Exception e) {
// TODO 异常处理
} finally {
// 最终操作释放锁
redisService.del(key);
}
}
复制代码
4. 问题
setnx
和expire
非原子性
线程A和线程B共同调用该锁
线程A-->执行 setnx
方法后成功获得锁,未执行设置超时时间 expire
方法,节点1挂掉,锁就变成死锁
线程B-->无法获取该锁
解决:
使用 set
指令操作取代 setnx
指令,增加可选参数,再获取锁的时候添加超时时间
// key--订单id,1--某一value值,30--超时时间,NX--加锁
set(key, 1, 30,NX);
复制代码
del
操作误删锁
线程A和线程B共同调用该锁
线程A-->成功调用锁并设置超时时间,线程A执行过慢,锁过期自动释放后线程A还未执行完
线程B-->A未执行完但锁过期自动释放,B得到锁后正常执行,但A完成任务后继续执行 del
指令删除锁,此时B还没有执行完,A删除释放的B加的锁
解决:
在删除锁之前做判断是否为自己加的锁,加锁的时候可以把当前线程id作为value值存储在锁中
- 加锁:
String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX)
复制代码
- 解锁
if(threadId .equals(redisClient.get(key))){
del(key)
}
复制代码
- 该操作引发第三个问题-并发操作
判断和释放锁是两个独立操作,不具有原子性
需要解决线程A和线程B不能同时执行加锁代码块
获取锁的线程需要开启一个守护线程,用来给快要过期的锁续航,重新设置超时时间
线程A再快到锁超时时间的时候,守护线程执行 expire
指令重新设置超时时间,当线程A执行完后,显式关掉守护线程