ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问(也就是读操作),但不允许写线程和读线程、写线程和写线程同时访问。约束如下
读—读不互斥:读与读之间不阻塞
读—写:读阻塞写,写也会阻塞读
写—写:写写阻塞
相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。
看一下官方案例
lass CachedData { Object data; volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public void processCachedData() { rwl.readLock().lock();//1 if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock();//2 rwl.writeLock().lock();//3 try { // Recheck state because another thread might have,acquired write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // 在释放写锁之前通过获取读锁降级写锁(注意此时还没有释放写锁) rwl.readLock().lock();//4 } finally { // 释放写锁而此时已经持有读锁 rwl.writeLock().unlock();//5 } } try { use(data); } finally { rwl.readLock().unlock();//6 } } }
View Code多个线程同时访问该缓存对象时,都加上当前对象的读锁,之后其中某个线程优先查看data数据是否为空。【加锁顺序序号:1 】
当前查看的线程,如果发现没有值则释放读锁,然后立即加上写锁,准备写入缓存数据。(进入写锁的前提是当前没有其他线程的读锁或者写锁)【加锁顺序序号:2和3 】
为什么还会再次判断是否为空值(!cacheValid)是因为第二个、第三个线程获得读的权利时也是需要判断是否为空,否则会重复写入数据。
写入数据后先进行读锁的降级后再释放写锁。【加锁顺序序号:4和5】
最后数据数据返回前释放最终的读锁。【加锁顺序序号:6 】
如果不使用锁降级功能,如先释放写锁,然后获得读锁,在这个get过程中,可能会有其他线程竞争到写锁 或者是更新数据 则获得的数据是其他线程更新的数据,可能会造成数据的污染,即产生脏读的问题
public class ReadAndWriteLock {
private static ReentrantLock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
public ReadAndWriteLock setValue(int value) {
this.value = value;
return this;
}
private int value;