你是想了解为什么在Java中操作Redis缓存时使用读写锁吗?
1,为什么使用读写锁
2,实际应用场景
对帐单进行操作时,避免同一个订单被同时操作,操作场景如下
1,保存账单
2,线下结算
代码如下,如果把读写锁的代码注释掉,会存在什么问题?
考虑并发情况,不同场景的并发或同一个场景的并发
比如同一个场景,线下结算,并发操作。
加锁逻辑
lock
// todo
unlock
-----------------------
具体如下
lock
因为存在多个帐单,用set集合存储
先查一下,现在是否有其他人操作,即从缓存中查询。
有,继续判断是否有交集,有,终止
没有,把当前操作的账单号存入redis。代表他正在操作。
unlock
业务逻辑执行完毕,释放锁,从redis set集合中,删除
---------------------------
存在问题分析
流程分析:
如果不加锁,线下结算并发
线程1,单号1,判断是否已在处理,-》否,-〉加入redis set集合-〉执行业务逻辑
线程2,单号1,判断是否已在处理,redis存在,流程结束。
好像没有问题
放大量,推到极致
如果线程1,100条数据,读redis,判断set集合是否为空,为空直接加入,set集合。
不为空,判断新入参100条与set集合已存在的数据,是否包含交集,是,流程结束。否,加入redis set集合,(这里加入redis这个过程比较慢)....
与此同时
线程2,100条(与线程1有交集),同样做上面的判断,读redis(同时线程1还在写redis,交集部分还没有写入),这时候判断线程1中100条与线程2中的100条,虽然有交集,此时是判断不出来的。
这时候问题就出现了
解决方案
线程1在写redis时,线程2是不应该读redis的。写与读应该是互斥的。
怎么控制这个问题,就引入了读写锁。
读写锁怎么解决并发操作的
线程1在写,线程2,不能读,线程2怎么处理的?
排队,线程1释放写锁时。线程2,才能执行读锁逻辑
读写锁控制点
1,加锁时。读时,加读锁,写时,加写锁
2,释放锁时。加写锁,从redis删除元素。
总共加了一个读锁,两个写锁
代码
加锁
/**
* 缓存操作中的账单号
*
* @param billNoList
*/
@SuppressWarnings("unchecked")
public void inOperationBillNoCache(List<String> billNoList) {
log.info("inOperationBillNoCache--->billNoList:{}", JSON.toJSONString(billNoList));
// RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(Constant.LEASE_BILL_IN_OPERATION_KEY);
// RLock rLock = readWriteLock.readLock();
// rLock.lock();
// try {
if (redisService.hasKey(Constant.LEASE_BILL_IN_OPERATION_CACHE)) {
Set<String> inOperationBillNoSet = redisService.smembers(Constant.LEASE_BILL_IN_OPERATION_CACHE);
log.info("inOperationBillNoCache--->inOperationBillNoSet:{}", JSON.toJSONString(inOperationBillNoSet));
if (CollectionUtils.isNotEmpty(inOperationBillNoSet)) {
List<String> multipleOperationBillNoList = (List<String>) CollectionUtils.intersection(inOperationBillNoSet, billNoList);
throw new LeaseServiceException(ErrConstant.INVALID_DATAFILED, String.format("账单[%s]存在多人操作,请刷新后重试", String.join(",", multipleOperationBillNoList)));
}
}
// }
// } finally {
// rLock.unlock();
// }
// RLock writeLock = readWriteLock.writeLock();
// writeLock.lock();
// try {
billNoList.forEach(billNo -> redisService.sadd(Constant.LEASE_BILL_IN_OPERATION_CACHE, billNo));
// } finally {
// writeLock.unlock();
// }
}
释放锁
/**
* 删除操作完成的账单号缓存
*
* @param billNoList
*/
public void removeInOperationBillNoCache(List<String> billNoList) {
log.info("removeInOperationBillNoCache--->billNoList:{}", JSON.toJSONString(billNoList));
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(Constant.LEASE_BILL_IN_OPERATION_KEY);
RLock writeLock = readWriteLock.writeLock();
writeLock.lock();
try {
billNoList.forEach(billNo -> redisService.srem(Constant.LEASE_BILL_IN_OPERATION_CACHE, billNo));
} finally {
writeLock.unlock();
}
}
3,思考
上文第二点,分析到并发存在问题。需要加读写锁
读写锁作用
1,提高并发性能
加锁只会降低并发度,不加锁效率应该最高的。读锁等于没有加锁,写锁反而降低了并发度。
这里好像没有体现出,并发性能的提高
2,确认数据一致性
避免了读写冲突
数据隔离:多线程,1线程写时,其他线程无法读写。读时,其他线程不能写,只能读。