共计 6800 个字符,预计需要花费 17 分钟才能阅读完成。
这篇文章主要为大家分析了 redis 实现分布式重入锁的方法是什么的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随丸趣 TV 小编一起来看看,下面跟着丸趣 TV 小编一起深入学习“redis 实现分布式重入锁的方法是什么”的知识吧。
什么是不可重入锁?
即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到而阻塞。
什么是可重入锁?
可重入锁,也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。
就是同一个线程再次进入同样代码时,可以再次拿到该锁。
可重入锁作用?
防止在同一线程中多次获取锁而导致死锁发生。
注:在 java 的编程中 synchronized 和 ReentrantLock 都是可重入锁。
基于 synchronized 的可重入锁
步骤 1:双重加锁逻辑
public class SynchronizedDemo {
// 模拟库存 100
int count=100;
public synchronized void operation(){
log.info( 第一层锁: 减库存
// 模拟减库存
count--;
add();
log.info(下订单结束库存剩余:{} ,count);
}
private synchronized void add(){
log.info( 第二层锁:插入订单
try { Thread.sleep(1000*10);
} catch (InterruptedException e) { e.printStackTrace();
}
}
}
步骤 2:加个测试类
public static void main(String[] args) { SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
for (int i = 0; i 3; i++) {
int finalI = i;
new Thread(()- { log.info( ------- 用户 {} 开始下单 -------- , finalI);
synchronizedDemo.operation();
}).start();
}
}
步骤 3:测试
20:44:04.013 [Thread-2] INFO com.agan.redis.controller.SynchronizedController - ------- 用户 2 开始下单 --------
20:44:04.013 [Thread-1] INFO com.agan.redis.controller.SynchronizedController - ------- 用户 1 开始下单 --------
20:44:04.013 [Thread-0] INFO com.agan.redis.controller.SynchronizedController - ------- 用户 0 开始下单 --------
20:44:04.016 [Thread-2] INFO com.agan.redis.Reentrant.SynchronizedDemo - 第一层锁: 减库存
20:44:04.016 [Thread-2] INFO com.agan.redis.Reentrant.SynchronizedDemo - 第二层锁:插入订单
20:44:14.017 [Thread-2] INFO com.agan.redis.Reentrant.SynchronizedDemo - 下订单结束库存剩余:99
20:44:14.017 [Thread-0] INFO com.agan.redis.Reentrant.SynchronizedDemo - 第一层锁: 减库存
20:44:14.017 [Thread-0] INFO com.agan.redis.Reentrant.SynchronizedDemo - 第二层锁:插入订单
20:44:24.017 [Thread-0] INFO com.agan.redis.Reentrant.SynchronizedDemo - 下订单结束库存剩余:98
20:44:24.017 [Thread-1] INFO com.agan.redis.Reentrant.SynchronizedDemo - 第一层锁: 减库存
20:44:24.017 [Thread-1] INFO com.agan.redis.Reentrant.SynchronizedDemo - 第二层锁:插入订单
20:44:34.017 [Thread-1] INFO com.agan.redis.Reentrant.SynchronizedDemo - 下订单结束库存剩余:97
由于 synchronized 关键字修饰的是方法,所有加锁为实例对象:synchronizedDemo
运行结果可以看出减库存和插入订单都是每个线程都完整运行两个方法完毕,才能释放锁,其他线程才能拿锁,即是一个线程多次可以拿到同一个锁,可重入。所以 synchronized 也是可重入锁。
基于 ReentrantLock 的可重入锁
ReentrantLock,是一个可重入且独占式的锁,是一种递归无阻塞的同步锁。和 synchronized 关键字相比,它更灵活、更强大,增加了轮询、超时、中断等高级功能。
步骤 1:双重加锁逻辑
public class ReentrantLockDemo { private Lock lock = new ReentrantLock();
public void doSomething(int n){
try{
// 进入递归第一件事:加锁
lock.lock();
log.info(-------- 递归 {} 次 -------- ,n);
if(n =2){
try { Thread.sleep(1000*2);
} catch (InterruptedException e) { e.printStackTrace();
}
this.doSomething(++n);
}else{
return;
}
}finally { lock.unlock();
}
}
}
步骤 2:加个测试类
public static void main(String[] args) { ReentrantLockDemo reentrantLockDemo=new ReentrantLockDemo();
for (int i = 0; i 3; i++) {
int finalI = i;
new Thread(()- { log.info( ------- 用户 {} 开始下单 -------- , finalI);
reentrantLockDemo.doSomething(1);
}).start();
}
}
步骤 3:测试
20:55:23.533 [Thread-1] INFO com.agan.redis.controller.ReentrantController - ------- 用户 1 开始下单 --------
20:55:23.533 [Thread-2] INFO com.agan.redis.controller.ReentrantController - ------- 用户 2 开始下单 --------
20:55:23.533 [Thread-0] INFO com.agan.redis.controller.ReentrantController - ------- 用户 0 开始下单 --------
20:55:23.536 [Thread-1] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 1 次 --------
20:55:25.537 [Thread-1] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 2 次 --------
20:55:27.538 [Thread-1] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 3 次 --------
20:55:27.538 [Thread-2] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 1 次 --------
20:55:29.538 [Thread-2] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 2 次 --------
20:55:31.539 [Thread-2] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 3 次 --------
20:55:31.539 [Thread-0] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 1 次 --------
20:55:33.539 [Thread-0] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 2 次 --------
20:55:35.540 [Thread-0] INFO com.agan.redis.Reentrant.ReentrantLockDemo - -------- 递归 3 次 --------
运行结果可以看出,每个线程都可以多次加锁解锁的,ReentrantLock 是可重入的。
redis 如何实现分布式重入锁?
setnx 虽然可以实现分布式锁,但是不可重入,在一些复杂的业务场景,我们需要分布式重入锁时,
对于 redis 的重入锁业界还是有很多解决方案的,目前最流行的就是采用 Redisson。【相关推荐:Redis 视频教程】
什么是 Redisson?
Redisson 是 Redis 官方推荐的 Java 版的 Redis 客户端。
基于 Java 实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。
在网络通信上是基于 NIO 的 Netty 框架,保证网络通信的高性能。
在分布式锁的功能上,它提供了一系列的分布式锁;如:
可重入锁(Reentrant Lock)
公平锁(Fair Lock)
非公平锁(unFair Lock)
读写锁(ReadWriteLock)
联锁(MultiLock)
红锁(RedLock)
案例实战:体验 redis 分布式重入锁
步骤 1:Redisson 配置
Redisson 配置的可以查考:redis 分布式缓存(三十四)一一 SpringBoot 整合 Redission – 掘金 (juejin.cn)
https://juejin.cn/post/7057132897819426824
步骤 2:Redisson 重入锁测试类
public class RedisController {
@Autowired
RedissonClient redissonClient;
@GetMapping(value = /lock)
public void get(String key) throws InterruptedException { this.getLock(key, 1);
}
private void getLock(String key, int n) throws InterruptedException {
// 模拟递归,3 次递归后退出
if (n 3) {
return;
}
// 步骤 1:获取一个分布式可重入锁 RLock
// 分布式可重入锁 RLock : 实现了 java.util.concurrent.locks.Lock 接口,同时还支持自动过期解锁。 RLock lock = redissonClient.getLock(key);
// 步骤 2:尝试拿锁
// 1. 默认的拿锁
//lock.tryLock();
// 2. 支持过期解锁功能,10 秒钟以后过期自动解锁, 无需调用 unlock 方法手动解锁
//lock.tryLock(10, TimeUnit.SECONDS);
// 3. 尝试加锁,最多等待 3 秒,上锁以后 10 秒后过期自动解锁
// lock.tryLock(3, 10, TimeUnit.SECONDS);
boolean bs = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (bs) {
try {
// 业务代码
log.info(线程 {} 业务逻辑处理: {}, 递归{} ,Thread.currentThread().getName(), key,n);
// 模拟处理业务
Thread.sleep(1000 * 5);
// 模拟进入递归
this.getLock(key, ++n);
} catch (Exception e) { log.error(e.getLocalizedMessage());
} finally {
// 步骤 3:解锁
lock.unlock();
log.info(线程 {} 解锁退出 ,Thread.currentThread().getName());
}
} else { log.info( 线程 {} 未取得锁 ,Thread.currentThread().getName());
}
}
}
RLock 三个加锁动作:
lock.tryLock();
默认的拿锁
lock.tryLock(10, TimeUnit.SECONDS);
支持过期解锁功能,10 秒钟以后过期自动解锁
lock.tryLock(3, 10, TimeUnit.SECONDS);
尝试加锁,最多等待 3 秒,上锁以后 10 秒后过期自动解锁
区别:
lock.lock():阻塞式等待。默认加的锁都是 30s
锁的自动续期,如果业务超长,运行期间自动锁上新的 30s。不用担心业务时间长而导致锁自动过期被删掉(默认续期)
加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在 30s 内自动过期,不会产生死锁问题
lock()如果我们未指定锁的超时时间,就使用【看门狗默认时间】:lockWatchdogTimeout = 30 * 1000
原理:只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】, 每隔 10 秒都会自动的再次续期,续成 30 秒
lock.lock(10,TimeUnit.SECONDS):10 秒钟自动解锁, 自动解锁时间一定要大于业务执行时间
出现的问题:在锁时间到了以后,不会自动续期
原理:lock(10,TimeUnit.SECONDS)如果我们传递了锁的超时时间,就发送给 redis 执行脚本,进行占锁,默认超时就是我们制定的时间
最佳实战:
lock.lock(10,TimeUnit.SECONDS); 省掉看门狗续期操作,自动解锁时间一定要大于业务执行时间,手动解锁
步骤 3: 测试
访问 3 次:http://127.0.0.1:9090/lock?key=ljw
线程 http-nio-9090-exec- 1 业务逻辑处理: ljw, 递归 1
线程 http-nio-9090-exec- 2 未取得锁
线程 http-nio-9090-exec- 1 业务逻辑处理: ljw, 递归 2
线程 http-nio-9090-exec- 3 未取得锁
线程 http-nio-9090-exec- 1 业务逻辑处理: ljw, 递归 3
线程 http-nio-9090-exec- 1 解锁退出
线程 http-nio-9090-exec- 1 解锁退出
线程 http-nio-9090-exec- 1 解锁退出
通过测试结果:
nio-9090-exec- 1 线程,在 getLock 方法递归了 3 次, 即证明了 lock.tryLock 是可重入锁
只有当 nio-9090-exec- 1 线程执行完后,io-9090-exec-2 nio-9090-exec-3 未取得锁
因为 lock.tryLock(3, 10, TimeUnit.SECONDS),尝试加锁,最多等待 3 秒,上锁以后 10 秒后过期自动解锁
所以等了 3 秒都等不到,就放弃了
关于“redis 实现分布式重入锁的方法是什么”就介绍到这了, 更多相关内容可以搜索丸趣 TV 以前的文章,希望能够帮助大家答疑解惑,请多多支持丸趣 TV 网站!