<div align="center"><a href="https://moyifeng.blog.csdn.net/"> <img src="https://badgen.net/badge/MYF/莫逸风 BLOG/4ab8a1?icon=rss"></a><a href="https://github.com/1046895947"> <img src="https://badgen.net/badge/MYF/莫逸风 GitHub/4ab8a1?icon=github"></a></div>
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);
}
}
复制代码
评论