首頁  >  文章  >  資料庫  >  什麼是可重入鎖?詳解redis實現分散式重入鎖的方法

什麼是可重入鎖?詳解redis實現分散式重入鎖的方法

青灯夜游
青灯夜游轉載
2022-01-28 10:00:108993瀏覽

什麼是可重入鎖?怎麼實現重入鎖?以下這篇文章就來帶大家深入聊聊redis實現分散式重入鎖的方法,希望對大家有幫助!

什麼是可重入鎖?詳解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重入鎖定測試類別
    1. 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());
              }
          }
      }
    2. RLock三個加鎖動作:
    • #預設的拿鎖
lock.tryLock();

############支援過期解鎖功能,10秒鐘以後過期自動解鎖############lock.tryLock(10, TimeUnit.SECONDS);############### ###嘗試加鎖,最多等待3秒,上鎖以後10秒後過期自動解鎖################lock.tryLock(3, 10, TimeUnit.SECONDS);# #################區別:###
  • 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秒都等不到,就放弃了

总结

上面介绍了分布式重入锁的相关知识,证明了Redisson工具能实现了可重入锁的功能。其实Redisson工具包中还包含了读写锁(ReadWriteLock)和 红锁(RedLock)等相关功能,我们下篇文章再详细研究。

更多编程相关知识,请访问:编程入门!!

以上是什麼是可重入鎖?詳解redis實現分散式重入鎖的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除