【SimpleFunction系列二.3】Redisson分布式锁8种锁模式剖析

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 可重入锁就是我们前面讲解的Redis分布式锁的Redisson实现,对于延时、过期等功能,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。

2. Redisson分布式锁8种锁模式剖析

【Redisson分布式锁—官方文档】

本文前置文:

【SimpleFunction系列二.1】渐进式理解Redis分布式锁

【SimpleFunction系列二.2】SpringBoot注解整合Redisson分布式锁

2.1 创建测试类

@SpringBootTest
public class LockModelTests {
    @Autowired
    RedissonClient redissonClient;
}

2.2 可重入锁(Reentrant Lock)

基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口。

可重入锁就是我们前面讲解的Redis分布式锁的Redisson实现,对于延时、过期等功能,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。

看门狗默认设置锁的超时时间是30S,在消耗时间超过1/3时对锁进行续租,我们也可以通过修改Config.lockWatchdogTimeout来另行指定。

demo:不设置等待时间会一直等待,不设置租赁时间会启动看门狗机制

@Test
void reentrantLock(){
  // 工作时间15s,注意看Redis中的ddl会在过去1/3时间后续约。
  new Thread(()->{
    RLock lock = redissonClient.getLock("testLock");
    lock.lock();
    doSomething(true, lock, 15L, Thread.currentThread().getName());
  }).start();
  // 租赁时间10s,设置了租赁时间,看门狗失效,工作时间15s,释放锁会报IllegalMonitorStateException
  new Thread(()->{
    RLock lock = redissonClient.getLock("testLock");
    lock.lock(5, TimeUnit.SECONDS);
    doSomething(true, lock, 20L, Thread.currentThread().getName());
  }).start();
  // 等待时间45S,未设置租赁时间,看门狗生效
  new Thread(()->{
    RLock lock = redissonClient.getLock("testLock");
    boolean lockFlag = false;
    try {
      lockFlag = lock.tryLock(45L, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    doSomething(lockFlag, lock, 15L, Thread.currentThread().getName());
  }).start();
  // 等待时间50S,租赁时间5S,工作时间10S,释放锁会报IllegalMonitorStateException
  new Thread(()->{
    RLock lock = redissonClient.getLock("testLock");
    boolean lockFlag = false;
    try {
      lockFlag = lock.tryLock(50L,5L, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    doSomething(lockFlag, lock, 10L, Thread.currentThread().getName());
  }).start();
  Scanner scanner = new Scanner(System.in);
  scanner.nextLine();
}

2.3 公平锁(Fair Lock)

基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口。

公平锁保证了当多个Redisson客户端线程同事请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson 会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。

在锁争夺较多时,程序利用大量cas去获取锁非常消耗CPU,可以考虑设置为公平锁。在锁的抢夺较少的时候就没必要设置成公平锁,毕竟公平锁也是需要成本的。

demo:主要理解公平二字,demo几乎同上

@Test
void fairLock() {
  // 租赁时间5s,设置了租赁时间,看门狗失效,工作时间50s,释放锁会报IllegalMonitorStateException
  new Thread(()->{
    RLock lock = redissonClient.getFairLock("testLock");
    lock.lock(5, TimeUnit.SECONDS);
    doSomething(true, lock, 50L, Thread.currentThread().getName());
  }).start();
  // 工作时间15s,注意看Redis中的ddl会在过去1/3时间后续约。
  new Thread(()->{
    RLock lock = redissonClient.getFairLock("testLock");
    sleepThread(1);
    lock.lock();
    doSomething(true, lock, 15L, Thread.currentThread().getName());
  }).start();
  // 等待时间45S,未设置租赁时间,看门狗生效
  new Thread(()->{
    RLock lock = redissonClient.getFairLock("testLock");
    boolean lockFlag = false;
    try {
      sleepThread(2);
      lockFlag = lock.tryLock(100L, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    doSomething(lockFlag, lock, 15L, Thread.currentThread().getName());
  }).start();
  // 等待时间50S,租赁时间5S,工作时间10S,释放锁会报IllegalMonitorStateException
  new Thread(()->{
    RLock lock = redissonClient.getFairLock("testLock");
    boolean lockFlag = false;
    try {
      sleepThread(3);
      lockFlag = lock.tryLock(50L,5L, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    doSomething(lockFlag, lock, 10L, Thread.currentThread().getName());
  }).start();
  Scanner scanner = new Scanner(System.in);
  scanner.nextLine();
}

2.4 联锁(MultiLock)

基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。

联锁指的是:同时对多个资源进行加锁操作,只有所有资源都加锁成功的时候,联锁才会成功。

@Test
void multiLockTest(){
  RLock lock = redissonClient.getFairLock("testLock");
  RLock lock2 = redissonClient.getFairLock("testLock2");
  RLock lock3 = redissonClient.getFairLock("testLock3");
  RedissonMultiLock multiLock = new RedissonMultiLock(lock, lock2, lock3);
  // 同时加锁:testLock testLock2 testLock3
  // 所有的锁都上锁成功才算成功。
  try {
    boolean tryLock = multiLock.tryLock(1, TimeUnit.SECONDS);
    doSomething(tryLock, multiLock, 10, Thread.currentThread().getName());
  } catch (InterruptedException e) {
    throw new RuntimeException(e);
  }
}

2.5 红锁(RedLock)

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。

与联锁比较相似,都是对多个资源进行加锁,但是红锁与连锁不同的是,红锁只需要在大部分资源加锁成功即可,大部分是指n/2+1。

@Test
void testRedLock(){
  new Thread(()->{
    RLock lock = redissonClient.getLock("testLock");
    lock.lock();
    doSomething(true, lock, 100, Thread.currentThread().getName());
  }).start();
  sleepThread(1);
  RLock lock = redissonClient.getLock("testLock");
  RLock lock2 = redissonClient.getLock("testLock2");
  RLock lock3 = redissonClient.getLock("testLock3");
  RedissonRedLock redLock = new RedissonRedLock(lock, lock2, lock3);
  boolean flag = redLock.tryLock();
  doSomething(flag, redLock, 10, Thread.currentThread().getName());
}

2.6 读写锁(ReadWriteLock)

基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。

读写锁:允许多个线程同时读,但是只允许一个线程写,在线程获取到写锁的时候,其他写操作和读操作都会处于阻塞状态,读锁和写锁也是互斥的,所以在读的时候是不允许写的。

@Test
void readWriteLockTest(){
  new Thread(()->{
    RLock writeLock = redissonClient.getReadWriteLock("testLock").writeLock();
    writeLock.lock();
    doSomething(true,writeLock,10,Thread.currentThread().getName());
  }).start();
  sleepThread(1);
  new Thread(()->{
    RLock readLock = redissonClient.getReadWriteLock("testLock").readLock();
    readLock.lock();
    doSomething(true,readLock,10,Thread.currentThread().getName());
  }).start();
  new Thread(()->{
    RLock readLock = redissonClient.getReadWriteLock("testLock").readLock();
    readLock.lock();
    doSomething(true,readLock,10,Thread.currentThread().getName());
  }).start();
  Scanner scanner = new Scanner(System.in);
  scanner.nextLine();
}

2.7 信号量(Semaphore)

基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口。

信号量(英语: semaphore)又称为 信号标,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。——维基百科
@Test
void semaphoreLockTest(){
  RSemaphore testLock = redissonClient.getSemaphore("testLock");
  int permits = testLock.availablePermits();
  testLock.addPermits(2-permits);
  new Thread(()->{
    RSemaphore semaphore = redissonClient.getSemaphore("testLock");
    try {
      semaphore.acquire();
      System.out.println("获取到一个许可"+Thread.currentThread().getName());
      sleepThread(10);
      semaphore.release();
      System.out.println("释放一个许可"+Thread.currentThread().getName());
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }).start();
  new Thread(()->{
    RSemaphore semaphore = redissonClient.getSemaphore("testLock");
    try {
      semaphore.acquire();
      System.out.println("获取到一个许可"+Thread.currentThread().getName());
      sleepThread(10);
      semaphore.release();
      System.out.println("释放一个许可"+Thread.currentThread().getName());
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }).start();
  new Thread(()->{
    RSemaphore semaphore = redissonClient.getSemaphore("testLock");
    try {
      semaphore.acquire();
      System.out.println("获取到一个许可"+Thread.currentThread().getName());
      sleepThread(10);
      semaphore.release();
      System.out.println("释放一个许可"+Thread.currentThread().getName());
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }).start();
  Scanner scanner = new Scanner(System.in);
  scanner.nextLine();
}

2.8 可过期信号量(PermitExpirableSemaphore)

基于Redis的Redisson可过期性信号量(PermitExpirableSemaphore)是在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放。它提供了异步(Async)反射式(Reactive)RxJava2标准的接口。

@Test
void permitExpirableSemaphoreLockTest() {
  RPermitExpirableSemaphore expirableSemaphore = redissonClient.getPermitExpirableSemaphore("PermitExpirableSemaphore");
  int permits = expirableSemaphore.availablePermits();
  expirableSemaphore.addPermits(2-permits);
  // 获取一个信号,有效期只有2秒钟。
  try {
    String permitId = expirableSemaphore.acquire(2, TimeUnit.SECONDS);
    System.out.println(permitId);
  } catch (InterruptedException e) {
    throw new RuntimeException(e);
  }
  try {
    sleepThread(5);
    String permitId = expirableSemaphore.acquire();
    System.out.println(permitId);
    expirableSemaphore.release(permitId);
  } catch (InterruptedException e) {
    throw new RuntimeException(e);
  }
  Scanner scanner = new Scanner(System.in);
  scanner.nextLine();
}

2.9 闭锁(CountDownLatch)

基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。

闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。
@Test
void countDownLatchTest(){
  RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("CountDownLatch");
  countDownLatch.trySetCount(1);
  new Thread(()->{
    RCountDownLatch downLatch = redissonClient.getCountDownLatch("CountDownLatch");
    sleepThread(10);
    downLatch.countDown();
  }).start();
  try {
    countDownLatch.await();
    System.out.println("主线程休眠结束");
  } catch (InterruptedException e) {
    throw new RuntimeException(e);
  }
}

2.9 LockModelTests.java

目录
相关文章
|
7月前
|
NoSQL 调度 Redis
分布式锁—3.Redisson的公平锁
Redisson公平锁(RedissonFairLock)是一种基于Redis实现的分布式锁,确保多个线程按申请顺序获取锁,从而实现公平性。其核心机制是通过队列和有序集合管理线程的排队顺序。加锁时,线程会进入队列并等待,锁释放后,队列中的第一个线程优先获取锁。RedissonFairLock支持可重入加锁,即同一线程多次加锁不会阻塞。新旧版本在排队机制上有所不同,新版本在5分钟后才会重排队列,而旧版本在5秒后就会重排。释放锁时,Redisson会移除队列中等待超时的线程,并通知下一个排队的线程获取锁。通过这种机制,RedissonFairLock确保了锁的公平性和顺序性。
|
3月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
217 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
5月前
|
NoSQL Java Redis
基于Redisson和自定义注解的分布式锁实现策略。
在实现分布式锁时,保证各个组件配置恰当、异常处理充足、资源清理彻底是至关重要的。这样保障了在分布布局场景下,锁的正确性和高效性,使得系统的稳健性得到增强。通过这种方式,可以有效预防并发环境下的资源冲突问题。
275 29
|
4月前
|
NoSQL Redis
分布式锁设计吗,你是如何实现锁类型切换、锁策略切换基于限流的?
本方案基于自定义注解与AOP实现分布式锁,支持锁类型(如可重入锁、公平锁等)与加锁策略(如重试、抛异常等)的灵活切换,并结合Redisson实现可重入、自动续期等功能,通过LUA脚本保障原子性,兼顾扩展性与实用性。
89 0
|
5月前
|
缓存 NoSQL Java
【📕分布式锁通关指南 11】源码剖析redisson之读写锁的实现
Redisson 的 `RedissonReadWriteLock` 提供了高效的分布式读写锁实现,适用于读多写少的场景。通过 Redis 与 Lua 脚本结合,确保读锁并行、写锁互斥,以及读写之间的互斥,保障了分布式环境下的数据一致性。它支持可重入、自动过期和锁释放机制,提升了系统并发性能与资源控制能力。
114 0
|
8月前
|
负载均衡 NoSQL 算法
Redisson分布式锁数据一致性解决方案
通过以上的设计和实现, Redisson能够有效地解决分布式环境下数据一致性问题。但是, 任何技术都不可能万无一失, 在使用过程中还需要根据实际业务需求进行逻辑屏障的设计和错误处理机制的建立。
369 48
|
7月前
|
NoSQL 调度 Redis
分布式锁—5.Redisson的读写锁
Redisson读写锁(RedissonReadWriteLock)是Redisson提供的一种分布式锁机制,支持读锁和写锁的互斥与并发控制。读锁允许多个线程同时获取,适用于读多写少的场景,而写锁则是独占锁,确保写操作的互斥性。Redisson通过Lua脚本实现锁的获取、释放和重入逻辑,并利用WatchDog机制自动续期锁的过期时间,防止锁因超时被误释放。 读锁的获取逻辑通过Lua脚本实现,支持读读不互斥,即多个线程可以同时获取读锁。写锁的获取逻辑则确保写写互斥和读写互斥,即同一时间只能有一个线程获取写锁,
376 17
|
NoSQL 安全 调度
【📕分布式锁通关指南 10】源码剖析redisson之MultiLock的实现
Redisson 的 MultiLock 是一种分布式锁实现,支持对多个独立的 RLock 同时加锁或解锁。它通过“整锁整放”机制确保所有锁要么全部加锁成功,要么完全回滚,避免状态不一致。适用于跨多个 Redis 实例或节点的场景,如分布式任务调度。其核心逻辑基于遍历加锁列表,失败时自动释放已获取的锁,保证原子性。解锁时亦逐一操作,降低死锁风险。MultiLock 不依赖 Lua 脚本,而是封装多锁协调,满足高一致性需求的业务场景。
253 0
【📕分布式锁通关指南 10】源码剖析redisson之MultiLock的实现
|
7月前
|
算法 NoSQL Redis
分布式锁—4.Redisson的联锁和红锁
Redisson的MultiLock和RedLock机制为分布式锁提供了强大的支持。MultiLock允许一次性锁定多个资源,确保在更新这些资源时不会被其他线程干扰。它通过将多个锁合并为一个大锁,统一进行加锁和释放操作。RedissonMultiLock的实现通过遍历所有锁并尝试加锁,若在超时时间内无法获取所有锁,则释放已获取的锁并重试。 RedLock算法则基于多个Redis节点的加锁机制,确保在大多数节点上加锁成功即可。RedissonRedLock通过重载MultiLock的failedLocksLi
403 10

热门文章

最新文章