17.读写锁ReentrantWriteReadLock
2018-08-08 本文已影响0人
0x70e8
读写锁ReentrantWriteReadLock,基于AQS的锁机制,实现ReadWriteLock接口。内部有两个锁,一个读锁和一个写锁,将AQS的state分成高16位和低16位作为信号量分给这两个锁使用。
特性
- 读锁共享
- 写锁互斥(也和读锁互斥)
- 可重入,写锁递归重入最大个数为1<< 16,读锁共享或递归重入最大数也是1<<16(因为共用了int型的state)
- 写锁可以降级为读锁,反之不行。
读写锁的使用主要在于其读锁可以共享,使得读操作较多的操作能高效并发。
读写锁的使用
- 创建锁对象
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
- 读写锁保护临界区
readLock.lock();
try{
// 只读操作
}finally{
readLock.unlock();
}
writeLock.lock();
try{
//写操作
}finally{
writeLock.unlock();
}
- 写锁降级(锁降级是为了让读线程及时感知到数据的变化。)
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) {
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 {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
如果上面示例代码不使用锁降级,只在最后的finally中释放写锁,那么当数据在本线程中修改之后,其他的读线程并不能感知,因为他们都阻塞了,数据的改变能一直维持到此线程释放写锁,其他读线程才有可能读到此线程写的值,但仅仅是可能,因为此线程释放写锁后,可能一个写线程抢占了锁,又一次修改了值,那么这个线程的修改其实对其他读线程来说,可能是不可见的。
原理
读写锁的原理简单来说就是组合了AQS的独占锁和共享锁,并且其内部有一些协作,比如锁降级,写锁和读锁的互斥等。