一、可重入锁(Reentrant Lock)
- 大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。
- 为了避免这种情况的发生,Redisson内部提供了一个
监控锁的看门狗
,它的作用是在Redisson实例被关闭前
,不断的延长锁的有效期
。 - 默认情况下,看门狗的检查
锁的超时时间是30秒钟
,也可以通过修改Config.lockWatchdogTimeout
来另行指定。
java
/**
* 方式一: 锁操作,操作库存
*
* @param number 数量
* @param operationType 操作类型
* @return {@link Integer}
*/
private Integer stockOperationByLock(Integer number, OperationType operationType) {
String currentThreadName = Thread.currentThread().getName();
long currentTimeMillis = System.currentTimeMillis();
String threadName = currentThreadName + "-" + currentTimeMillis;
// 获取锁
RLock rlock = redissonClient.getLock(STOCK_LOCK_KEY);
long stockTotal = 0L;
try {
rlock.lock();
log.info("线程名称:{} => 获取锁是否成功: {}", threadName, true);
// 验证: 库存
Stock stock = this.verifyStockParamReturn(number, operationType);
// 计算: 库存数量
stockTotal = this.computeStockNum(threadName, stock.getQuantity(), number, operationType);
log.info("线程名称:{} => 当前库存总数: {}", threadName, stockTotal);
// 更新: 库存数量
updateQuantity(1L, stockTotal);
} finally {
if (rlock != null) {
rlock.unlock();
}
}
return (int) stockTotal;
}
- 通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了
java
/**
* 方式二: 通过尝试获取锁操作,操作库存
*
* @param number 数量
* @param operationType 操作类型
* @return {@link Integer}
*/
private Integer stockOperationByTryLock(Integer number, OperationType operationType) {
String currentThreadName = Thread.currentThread().getName();
long currentTimeMillis = System.currentTimeMillis();
String threadName = currentThreadName + "-" + currentTimeMillis;
// 获取锁
RLock rlock = redissonClient.getLock(STOCK_LOCK_KEY);
long stockTotal = 0L;
try {
// 尝试上锁, 锁的等待时间 10 秒, 获取锁后的执行时间为 30秒(默认)自动解锁
boolean isLocked = rlock.tryLock(10, TimeUnit.SECONDS);
// 尝试上锁, 锁的等待时间 10 秒, 获取锁后的执行时间为 40 秒 自动解锁
// boolean isLocked = rlock.tryLock(10, 40,TimeUnit.SECONDS);
log.info("线程名称:{} => 获取锁是否成功: {}", threadName, isLocked);
if (!isLocked) {
throw new RuntimeException("系统繁忙, 请稍后再试!");
}
// 验证: 库存
Stock stock = this.verifyStockParamReturn(number, operationType);
// 计算: 库存数量
stockTotal = this.computeStockNum(threadName, stock.getQuantity(), number, operationType);
log.info("线程名称:{} => 当前库存总数: {}", threadName, stockTotal);
// 更新: 库存数量
updateQuantity(1L, stockTotal);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (rlock != null) {
rlock.unlock();
}
}
return (int) stockTotal;
}
java
/**
*
* 方式三: 通过异步锁,同步更新Redis库存数量
*
* @param number 数量
* @param operationType 操作类型
* @return {@link Integer}
*/
private void syncUpdateRedisStockByLockAsync(Long stockTotal, OperationType operationType) {
// 获取锁
RLock rlock = redissonClient.getLock(STOCK_LOCK_KEY);
try {
// 尝试异步上锁, 锁的等待时间 10 秒, 获取锁后的执行时间为 30秒(默认)
RFuture<Boolean> future = rlock.tryLockAsync(10, 30, TimeUnit.SECONDS);
// 加锁完成后 异步执行
future.whenComplete((res, ex) -> {
// 更新 redis 库存信息
log.info("【异步上锁-更新Redis库存】 获取锁是否成功:{} 当前线程名称:{} 当前线程ID:{} ", future.isDone(), Thread.currentThread().getName(), Thread.currentThread().getId());
if (ex != null) {
log.error("【异步上锁-更新Redis库存】异常信息:{}", ex.getMessage());
return;
}
if (!future.isDone()) {
log.error("【异步上锁-更新Redis库存】获取异步锁失败:{}",future.isDone());
return;
}
stringRedisTemplate.opsForValue().set(STOCK_CACHE, stockTotal.toString());
log.error("【异步上锁-更新Redis库存】key:{} 更新库存数:{}",STOCK_CACHE, stockTotal);
});
} finally {
if (rlock != null) {
rlock.unlock();
}
}
return;
}
- 重入锁: 同一个锁key,在不同方法下的同一个当前线程中,可以获取锁并加锁。
- 每一次重入次数,都会加 1。
- 每一次释放次数,都会减 1.
java
/**
* 方式二: 通过尝试获取锁操作,操作库存
*
* @param number 数量
* @param operationType 操作类型
* @return {@link Integer}
*/
private Integer stockOperationByTryLock(Integer number, OperationType operationType) {
String currentThreadName = Thread.currentThread().getName();
long currentTimeMillis = System.currentTimeMillis();
String threadName = currentThreadName + "-" + currentTimeMillis;
// 获取锁
RLock rlock = redissonClient.getLock(STOCK_LOCK_KEY);
long stockTotal = 0L;
try {
// 尝试上锁, 锁的等待时间 10 秒, 获取锁后的执行时间为 30秒(默认)
boolean isLocked = rlock.tryLock(10, TimeUnit.SECONDS);
log.info("线程名称:{} => 获取锁是否成功: {}", threadName, isLocked);
if (!isLocked) {
throw new RuntimeException("系统繁忙, 请稍后再试!");
}
// 验证: 库存
Stock stock = this.verifyStockParamReturn(number, operationType);
// 计算: 库存数量
stockTotal = this.computeStockNum(threadName, stock.getQuantity(), number, operationType);
log.info("线程名称:{} => 当前库存总数: {}", threadName, stockTotal);
// 更新: 库存数量
updateQuantity(1L, stockTotal);
// 同步更新:redis 库存 (重入锁)
syncUpdateRedisStockByLockAsync(stockTotal, operationType);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (rlock != null) {
rlock.unlock();
}
}
return (int) stockTotal;
}
/**
*
* 方式三: 通过异步锁,同步更新Redis库存数量
*
* @param number 数量
* @param operationType 操作类型
* @return {@link Integer}
*/
private void syncUpdateRedisStockByLockAsync(Long stockTotal, OperationType operationType) {
// 获取锁
RLock rlock = redissonClient.getLock(STOCK_LOCK_KEY);
try {
// 尝试异步上锁, 锁的等待时间 10 秒, 获取锁后的执行时间为 30秒(默认)
RFuture<Boolean> future = rlock.tryLockAsync(10, 30, TimeUnit.SECONDS);
// 加锁完成后 异步执行
future.whenComplete((res, ex) -> {
// 更新 redis 库存信息
log.info("【异步上锁-更新Redis库存】 获取锁是否成功:{} 当前线程名称:{} 当前线程ID:{} ", future.isDone(), Thread.currentThread().getName(), Thread.currentThread().getId());
if (ex != null) {
log.error("【异步上锁-更新Redis库存】异常信息:{}", ex.getMessage());
return;
}
if (!future.isDone()) {
log.error("【异步上锁-更新Redis库存】获取异步锁失败:{}",future.isDone());
return;
}
stringRedisTemplate.opsForValue().set(STOCK_CACHE, stockTotal.toString());
log.error("【异步上锁-更新Redis库存】key:{} 更新库存数:{}",STOCK_CACHE, stockTotal);
});
} finally {
if (rlock != null) {
rlock.unlock();
}
}
return;
}
- 如图所属: 执行到
syncUpdateRedisStockByLockAsync()
方法后中的getLock()
方法后, 可以看出锁次数+1
。
二、公平锁(Fair Lock)
- 它保证了当多个Redisson客户端线程
同时请求加锁时
,优先分配给先发出请求的线程
,所有请求线程会在一个队列中排队
。 - 当某个线程出现
宕机时
,Redisson会等待5秒
后继续下一个线程
,也就是说如果前面有5个线程都处于等待状态
,那么后面的线程会等待至少25秒
。
java
RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();
java
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
fairLock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();
java
RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
三、联锁(MultiLock)
- 基于Redis的Redisson分布式
联锁RedissonMultiLock
对象可以将多个RLock对象关联为一个联锁
,每个RLock对象实例
可以来自于不同的Redisson实例
。
- 方式一
java
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 所有的锁都上锁成功才算成功。
lock.lock();
...
lock.unlock();
- 方式二
java
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);
// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
四、红锁(RedLock)
- 基于Redis的
Redisson红锁
RedissonRedLock对象实现了Redlock
介绍的加锁算法
。 - 该对象也可以用来
将多个RLock对象
关联为一个红锁
,每个RLock对象实例
可以来自于不同的Redisson实例
。
java
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();
五、读写锁(ReadWriteLock)
读写锁 RReadWriteLock
: 允许同时有多个读锁
和一个写锁处于加锁状态
。- Java对象实现了
java.util.concurrent.locks.ReadWriteLock
接口
- 方式一:
java
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
- 方式二:
java
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();