ReadWriteLock
Java除了提供管程和信号量两种同步原语,还提供了ReadWriteLock,StampedLock等,目的是分场景优化性能,提升易用性。
读多写少是并发场景中非常普遍的,我们通常使用缓存来解决此类问题。
缓存之所以能提升性能,重要的条件就是缓存数据一定是读多写少。
Java SDK中为我们提供了读写锁ReadWriteLock,使用方便,性能也很好。
读写锁的特性
- 允许多个线程同事读取共享变量
- 只允许一个线程写共享变量
- 若一个写线程正在执行写操作,此时禁止读线程读共享变量
读写锁并不是Java特有的,而是一种通用技术
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock rLock = rwLock.readLock();
Lock wLock = rwLock.writeLock();
try {
...
} finally {
xxx.unLock();
}
利用读写锁作为缓存时,可以采用启动时加载的方式,或者按需加载的方式,取决于缓存数据量的大小
读写锁的升级与降级
- 读锁不能升级为写锁
- 写锁可以降级为读锁
写锁降级为读锁后,需要单独释放写锁和读锁
class CachedData {
Object data;
volatile boolean cacheValid;
final ReadWriteLock rwl = new ReentrantReadWriteLock();
// 读锁
final Lock r = rwl.readLock();
//写锁
final Lock w = rwl.writeLock();
void processCachedData() {
// 获取读锁
r.lock();
if (!cacheValid) {
// 释放读锁,因为不允许读锁的升级
r.unlock();
// 获取写锁
w.lock();
try {
// 再次检查状态
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 释放写锁前,降级为读锁
// 降级是可以的
r.lock();
} finally {
// 释放写锁
w.unlock();
}
}
// 此处仍然持有读锁
try {
use(data);
}finally {
r.unlock();}
}
}
总结
- 读写锁类似于ReentrantLock,也支持公平模式和非公平模式
- 读锁和写锁都实现了 java.util.concurrent.locks.Lock接口,支持lock()、tryLock()、 lockInterruptibly() 等方法
- 但是有一点需要注意,那就是只有写锁支持条件变量,读锁是不支持条件变量的
- 读锁调用newCondition()会抛出 UnsupportedOperationException异常
- ReadWriteLock 没能解决缓存数据和源头数据的同步问题,可以借助超时机制或手动触发的方式解决该问题