读写锁的出现原因
ReentrantLock
实现一种标准的互斥锁,每次最多只有一个线程能持有ReentrantLock
,限制了并发性,互斥是一种保守的加锁策略,虽然避免了“写/写”冲突和“写/读”冲突,但也避免了“读/读”冲突。
而大部分情况下读操作比较多,如果此时能够放宽加锁需求,允许多个读操作的线程同时访问数据结构,可以提升程序的性能(只要每个线程保证读取到最新的数据,并且在读取数据时不会有其他线程修改数据就行)
读锁的作用
任何锁表面上是互斥,但本质是都是为了避免原子性问题(如果程序没有原子性问题,那只用volatile来避免可见性和有序性问题就可以了,效率更高),读锁自然也是为了避免原子性问题。
比如一个long型参数的写操作并不是原子性的,如果允许同时读和写,那读到的数很可能是就是写操作的中间状态,比如刚写完前32位的中间状态。long型数都如此,而实际上一般读的都是复杂的对象,那中间状态的情况就更多了。
所以读锁是防止读到写的中间值。
读写锁的特点
-
如果有一个线程已经占用了
读锁
,则此时其他线程如果要申请读锁
,可以申请成功。 -
如果有一个线程已经占用了
读锁
,则此时其他线程如果要申请写锁
,则申请写锁的线程会一直等待释放读锁,因为读写不能同时操作。 -
如果有一个线程已经占用了
写锁
,则此时其他线程如果申请写锁
或者读锁
,都必须等待之前的线程释放写锁,同样也因为读写不能同时,并且两个线程不应该同时写。 -
锁降级 不可升级 从写锁降级到读锁不可升级(获取写锁->获取读锁->释放写锁)
总结
-
读读共享、其他都互斥(写写互斥、读写互斥、写读互斥)
-
加读锁是防止读取到中间值
示例代码
ReentrantReadWriteLock
源码上提供了两段demo代码
- 下面代码展示了如何在更新缓存后执行锁降级
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (