Java高并发之锁的使用以及原理浅析(8)

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;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/0e6f83f129bd3fde91f845da9f5e6d21.html