【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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
NoSQL 算法 安全
Redlock 算法-主从redis分布式锁主节点宕机锁丢失的问题
Redlock 算法-主从redis分布式锁主节点宕机锁丢失的问题
152 0
|
3月前
|
存储 安全 JavaScript
【分布式技术专题】「授权认证体系」深度解析OAuth2.0协议的原理和流程框架实现指南(授权流程和模式)
在传统的客户端-服务器身份验证模式中,客户端请求服务器上访问受限的资源(受保护的资源)时,需要使用资源所有者的凭据在服务器上进行身份验证。资源所有者为了给第三方应用提供受限资源的访问权限,需要与第三方共享它的凭据。这就导致一些问题和局限:
372 2
【分布式技术专题】「授权认证体系」深度解析OAuth2.0协议的原理和流程框架实现指南(授权流程和模式)
|
2月前
|
Java 数据库连接 API
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
54 0
|
开发框架 Java 数据库连接
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)(下)
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
37 0
|
数据库 微服务
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)(上)
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
39 0
|
存储 关系型数据库 MySQL
分布式事物【悲观锁、乐观锁、读锁、写锁、间隙锁、临键锁 、 表锁、行锁、页面锁、 如何避免死锁】(二)-全面详解(学习总结---从入门到深化)
分布式事物【悲观锁、乐观锁、读锁、写锁、间隙锁、临键锁 、 表锁、行锁、页面锁、 如何避免死锁】(二)-全面详解(学习总结---从入门到深化)
48 0
|
11天前
|
存储 分布式数据库
GaussDB分布式与单机模式的比较
【4月更文挑战第7天】GaussDB分布式与单机模式的比较
1609 5
|
12天前
|
缓存 NoSQL 数据库
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
28 0
|
22天前
|
存储 监控 安全
金石推荐 | 【分布式技术专题】「单点登录技术架构」一文带领你好好认识以下Saml协议的运作机制和流程模式
金石推荐 | 【分布式技术专题】「单点登录技术架构」一文带领你好好认识以下Saml协议的运作机制和流程模式
21 0

热门文章

最新文章