读写锁ReentrantReadWriteLock
读写锁
一、概述
有些时候使用lock锁进行同步会影响到性能效率问题。
比如说:读写锁,读锁只是读,而不会影响到数据安全问题;而写锁是会对数据造成安全问题,所以需要加锁。
但是读读是要支持并发的;读写只能是互斥的;写写也只能是互斥的;
所以为了提高并发性能问题,有了读写锁,下面来看下读写锁。
二、读写锁结构
用final修饰的读锁和写锁,在读写锁创建出来之后,就无法被修改。
而Sync是继承了AQS框架的。
三、官方案例分析
// 官方给的使用案例
class CachedData {
// 缓存数据
Object data;
// 缓存是否失效。默认无效
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
// 处理数据
void processCachedData() {
// 获取读锁
rwl.readLock().lock();
if (!cacheValid) {
// 缓存失效
// Must release read lock before acquiring write lock
// 但是为什么需要将读锁解开,然后再次上写锁。如果相对于同一个线程来说的话,没有必要解开读锁,然后上写锁
// 因为升级可能会导致死锁,在读写锁源码中直接拒绝了
// 先将读锁给释放掉,然后加上写锁
rwl.readLock().unlock();
// 加写锁 为了保证缓存是有效的,因为可能会在写的时候存在读的情况,如果是读的话,那么就可能会存在读取得到脏数据的情况,所以需要在上面加上读锁
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
// TODO 如果还是失效,那么需要将缓存进行更新
// data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
// 缓存有效
try {
// TODO
// use(data);
} finally {
rwl.readLock().unlock();
}
}
}
对于多个线程来抢夺锁来说,在某一时刻,只有一个线程能够拿到读锁或者是写锁。
如果某个线程拿到的是写锁,那么其他的线程想要读锁或者是写锁,那么就只能够阻塞等待线程释放写锁;
如果某个线程拿到的是读锁,那么其他的线程如果获取得到写锁,是没有问题的。因为读锁是支持并发的;但是如果有一个线程需要获取得到的是写锁,那么就需要等待获取得到读锁的释放掉,才可以得到写锁。
3.1、为什么要在读锁释放之后再加写锁?
看到上面的代码觉得很奇怪,为什么加上读锁判断缓存失效之后,先将读锁释放掉,然后加上写锁。
为什么获取得到了读锁之后,不能再获取得到写锁?
不能够在持有读锁情况下,再持有写锁情况。但是要是在单线程条件下,理论上来说,是可以的。但是读写锁在源码层面上就直接禁止掉了这种方式。是因为存在死锁问题。
可能存在的情况:
t1 r t2 r
需求:t1和t2在持有读锁的情况下,又都想去获取得到写锁
然后t1在读锁中想要获取写锁,那么就必须要等到写锁释放了之后才能够获取得到写锁;而t2持有了读锁的时候,t1就无法持有,需要等待;而t2想要获取得到锁,就需要等待t1将锁释放掉。结果就是造成了死锁。
所以读写锁中不允许出现在持有读锁的情况下,再来持有写锁的情况。
但是在持有写锁的情况下,是可以持有读锁的。
t1 w t2 w 写锁和写锁之间是互斥的,所以在某一个时刻,只有一个写锁成功,假如说是t1锁;那么获取得到了写锁之后,读锁就不能够被其他线程持有了。只能是本线程来持有,所以是可以的。当前线程是可以操作的。
四、读写锁的升级和降级
上面已经讲过了原理,下面来看下例子:
4.1、持有读锁在持有写锁-(升级)
public class ReadWriteLockDemoOne {
private final static Logger logger = LoggerFactory.getLogger(ReadWriteLockDemoOne.class);
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public static void main(String[] args) {
new Thread(()->{
reentrantReadWriteLock.readLock().lock();
logger.info("获取得到了读锁");
reentrantReadWriteLock.writeLock().lock();
logger.info("在获取得到读锁的情况下来获取得到写锁");
reentrantReadWriteLock.writeLock().unlock();
logger.info("在读锁的情况下释放了读锁");
reentrantReadWriteLock.readLock().unlock();
logger.info("释放读锁");
}).start();
}
}
4.2、持有了写锁再持有读锁-(降级)
public class ReadWriteLockDemoTwo {
private final static Logger logger = LoggerFactory.getLogger(ReadWriteLockDemoTwo.class);
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public static void main(String[] args) {
new Thread(()->{
reentrantReadWriteLock.writeLock().lock();
logger.info("获取得到了写锁");
reentrantReadWriteLock.readLock().lock();
logger.info("在获取得到写锁的情况下来获取得到读锁");
reentrantReadWriteLock.readLock().unlock();
logger.info("在获取得到写锁的情况下来释放读锁");
reentrantReadWriteLock.writeLock().unlock();
logger.info("释放写锁");
}).start();
}
}
五、读写锁源码简单分析
首先来分析下,为什么在持有读锁的情况下,不能够持有写锁。
首先看下伪代码:
reentrantReadWriteLock.readLock().lock();
logger.info("获取得到了读锁");
reentrantReadWriteLock.writeLock().lock();
看下writeLock().lock()的实现:
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
// 是否有些锁的持有,有读锁,那么肯定没有写锁
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
// 直接返回了。源码决定了
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-10-19 多线程之lock锁