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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
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
目录
相关文章
|
5月前
|
存储 缓存 NoSQL
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
redis分布式锁、redisson、可重入、主从一致性、WatchDog、Redlock红锁、zookeeper;Redis集群、主从复制,全量同步、增量同步;哨兵,分片集群,Redis为什么这么快,I/O多路复用模型——用户空间和内核空间、阻塞IO、非阻塞IO、IO多路复用,Redis网络模型
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
|
4月前
|
存储 消息中间件 Apache
比较微服务中的分布式事务模式
比较微服务中的分布式事务模式
76 2
|
13天前
|
存储 运维 NoSQL
分布式读写锁的奥义:上古世代 ZooKeeper 的进击
本文作者将介绍女娲对社区 ZooKeeper 在分布式读写锁实践细节上的思考,希望帮助大家理解分布式读写锁背后的原理。
|
1月前
|
监控
Saga模式在分布式系统中保证事务的隔离性
Saga模式在分布式系统中保证事务的隔离性
|
2月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
67 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
2月前
|
NoSQL Java Redis
开发实战:使用Redisson实现分布式延时消息,订单30分钟关闭的另外一种实现!
本文详细介绍了 Redisson 延迟队列(DelayedQueue)的实现原理,包括基本使用、内部数据结构、基本流程、发送和获取延时消息以及初始化延时队列等内容。文章通过代码示例和流程图,逐步解析了延迟消息的发送、接收及处理机制,帮助读者深入了解 Redisson 延迟队列的工作原理。
|
3月前
|
缓存 NoSQL Java
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
缓存与分布式锁、Redisson分布式锁、缓存数据一致性【必须满足最终一致性】
145 14
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
|
6月前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
237 0
|
2月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
71 4
|
3月前
Saga模式在分布式系统中如何保证事务的隔离性
Saga模式在分布式系统中如何保证事务的隔离性