Java高并发系列之ReentrantReadWriteLock源码分析

Posted 码农的修炼之道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java高并发系列之ReentrantReadWriteLock源码分析相关的知识,希望对你有一定的参考价值。

       上一篇文章《》我们分析了独占锁的实现,知道这是一种独占锁(排他锁)。这种锁比较粗,后续我们分析并发包下的LinkedBlockingQueue等队列时会看到,全是依赖这种独占锁。这里先不介绍。


      实际上,在多线程并发环境下,读数据不会对数据造成修改,因此不需要加锁就可以。在ReentrantReadWriteLock类中,将读操作和写操作拆分为两把锁,分别是读锁:ReadLock和写锁:WriteLock。这种读写分离的操作,运行多线程同时读取数据,在写操作的时候,才是独占的。


   我们先来看看ReentrantReadWriteLock的类图,了解其结构。这样我们看源码也能一目了然,印象也会深刻。

      其实,这个类图可以看出,ReentrantReadWriteLock实现了ReadWriteLock和Serializable接口。其内部有个Sync类继承自AQS,同时这个类需要实现公平锁和非公平锁,因此Sync有两个子类:FairSync和NonfairSync类。

     这里说明一下,ReentrantReadWriteLock里面有两个内部类:ReadLock和WriteLock,是因为要实现ReadWriteLock的接口的。在ReadWriteLock接口中定义了ReadLock和WriteLock的方法用于返回读锁和写锁。


    ReentrantReadWriteLock的实现和之前的ReentrantLock有点像。首先,我们来看看其构造函数。其空参数的构造函数默认是使用内部的非公平锁,还可以传入参数,指定创建公平或非公平锁,源码如下所示。

Java高并发系列之ReentrantReadWriteLock源码分析(一)


     再扯远一点,在ReentrantReadWriteLock中有个方法:boolean isFair(),这个类似于Netty框架中判断一个Handler是inBound还是OutBound类型,也是这样判断的。源码如下:

Java高并发系列之ReentrantReadWriteLock源码分析(一)


        下面进入正题,下面来看读锁的源码。读锁中最重要的两个方法就是lock和unlock方法。下面我们先来看看lock方法源码。

Java高并发系列之ReentrantReadWriteLock源码分析(一)

       这个和之前ReentrantLock实现类似,由于读锁支持多线程同时访问,因此这里使用AQS中的共享锁。我们继续跟进去看看acquireShared()源码。

Java高并发系列之ReentrantReadWriteLock源码分析(一)

      其中,tryAcquireShared()需要AQS的子类去实现,这里在Sync类中实现了。而doAcquireShared方法是在AQS中实现的。下面我们来看看tryAcquireShared源码。

Java高并发系列之ReentrantReadWriteLock源码分析(一)

    这里主要分为以下几步:

1、首先获取State的值,然后调用exclusiveCount方法来判断写锁是否占用 ,当该方法返回值不为0,就说明写锁被占用。这里如果当前线程获取写锁,那么也是允许获取读锁的。

2、获取读锁的次数,也就是调用sharedCount方法进行计算。这里注意,写锁占用了State变量的低16位,读锁占用高16位。

3、这里readShouldBlock方法在Sync中是抽象方法,需要FairSync和NonFairSync去实现。

Java高并发系列之ReentrantReadWriteLock源码分析(一)

       如果是公平锁,实现如下。原理是去查看AQS的阻塞队列,如果队列里面有挂起的线程,那么返回true,那么上面的tryAcquireShared函数直接进入最后fullTryAcquireShared去自旋获取锁。

Java高并发系列之ReentrantReadWriteLock源码分析(一)

      如果是非公平锁,实现如下。其原理是判断队列中是否有元素在获取写锁。

Java高并发系列之ReentrantReadWriteLock源码分析(一)

Java高并发系列之ReentrantReadWriteLock源码分析(一)

4、继续回到tryAcquireShared函数,如果成功获取读锁,那么如果r=0,那就是第一次获取读锁,这里用firstReadHoldCount来记录同一个线程读的次数。HoldCount用于记录其他线程的读次数。


    下面来看看unlock的源码。

Java高并发系列之ReentrantReadWriteLock源码分析(一)

    进入releaseShared源码看看。

       这里tryReleaseShared需要AQS的子类实现,这里在Sync中实现。该方法的主要作用是在for(;;)里面,用CAS操作设置state的值为nextc,nextc也就是减去一个读单位的值。如果nextc为0,说明当前没有线程占用读锁,那么返回true。然后releaseShared函数进入doReleaseShared,去唤醒其他因为写操作阻塞的线程(比如其他线程获取写锁,那么读操作和写操作不能同时进行,读操作线程会阻塞)。


总结ReentrantReadWriteLock将读写锁分离,当读操作时,不阻塞。但是读写操作或者写写操作会进行阻塞,此时相当于独占锁。读写分离的好处在于有些场景是读多写少的情况。用这种读写分离的方式有利于提高并发程度。

      此外,ReentrantReadWriteLock也分为公平和非公平。这个可以在构造函数中设置,默认是非公平ReentrantReadWriteLock也允许同一个线程既获取读锁,也占有写锁情况。这个在源码中也有体现。


下一篇,我们看看ReentrantReadWriteLock写锁的源码。

以上是关于Java高并发系列之ReentrantReadWriteLock源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Java——多线程高并发系列之LockReentrantLock

Java——多线程高并发系列之synchronized关键字

Java——多线程高并发系列之synchronized关键字

Java——多线程高并发系列之ThreadLocal的使用

Java——多线程高并发系列之ThreadLocal的使用

Java——多线程高并发系列之ReadWriteLock读写锁