什么是可重入锁?怎么实现重入锁?下面本篇文章就来带大家深入聊聊redis实现分布式重入锁的方法,希望对大家有所帮助!
即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到而阻塞。
可重入锁,也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。 就是同一个线程再次进入同样代码时,可以再次拿到该锁。
防止在同一线程中多次获取锁而导致死锁发生。
注:在java的编程中synchronized 和 ReentrantLock都是可重入锁。
步骤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
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次--------
setnx虽然可以实现分布式锁,但是不可重入,在一些复杂的业务场景,我们需要分布式重入锁时,
对于redis的重入锁业界还是有很多解决方案的,目前最流行的就是采用Redisson
。【相关推荐:Redis视频教程】
什么是Redisson?
案例实战:体验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(3, 10, TimeUnit.SECONDS);
区别:
最佳实战:
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解锁退出
通过测试结果:
上面介绍了分布式重入锁的相关知识,证明了Redisson工具能实现了可重入锁的功能。其实Redisson工具包中还包含了读写锁(ReadWriteLock)和 红锁(RedLock)等相关功能,我们下篇文章再详细研究。
更多编程相关知识,请访问:编程入门!!
以上是什么是可重入锁?详解redis实现分布式重入锁的方法的详细内容。更多信息请关注PHP中文网其他相关文章!