ホームページ  >  記事  >  データベース  >  リエントラントロックとは何ですか? Redis が分散再入ロックを実装する方法の詳細な説明

リエントラントロックとは何ですか? Redis が分散再入ロックを実装する方法の詳細な説明

青灯夜游
青灯夜游転載
2022-01-28 10:00:108963ブラウズ

リエントラントロックとは何ですか?再入ロックを実装するにはどうすればよいですか?次の記事では、redis が分散再入ロックを実装する方法について詳しく説明します。

リエントラントロックとは何ですか? 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
  • キーワードはメソッドを変更し、すべてのロックはインスタンス オブジェクトです: synchronizedDemo
  • 実行結果は、ロックを解放する前に各スレッドが 2 つのメソッドを完全に実行する必要があることを示しています。ロックを取得できるのは他のスレッドだけです。同じロックを複数回取得でき、再入可能です。したがって、同期は再入可能なロックでもあります。

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 が公式に推奨する Redis クライアントの Java バージョンです。
  • Java ユーティリティ ツールキットの共通インターフェイスに基づいて、分散特性を持つ一般的に使用される一連のツール クラスをユーザーに提供します。
  • ネットワーク通信に関しては、NIO の Netty フレームワークに基づいており、ネットワーク通信の高いパフォーマンスを保証します。
  • 分散ロックの機能に関しては、
    • リエントラント ロック
    • フェア ロック ロック)
    • などの一連の分散ロックを提供します。 unFair Lock(アンフェア ロック)
    • ReadWriteLock(ReadWriteLock)
    • Interlock(MultiLock)
    • Red Lock(RedLock)

ケース コンバット: Redis 分散リエントランシー ロックを体験する

ステップ 1: Redisson 構成

Redisson 構成確認できます: redis 分散キャッシュ (34) - SpringBoot は Redission を統合します - Nuggets (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());
        }
    }
}

R3 つのロック アクションをロックします:

    1. デフォルト ロック
      lock.tryLock();
    1. 期限切れのロック解除機能をサポートし、期限切れから 10 秒後に自動的にロックが解除されます
      lock.tryLock(10, TimeUnit.SECONDS);
    1. ロックを試行し、最大 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。