ReentrantReadWriteLock源码(jdk1.8)

ReentrantReadWriteLock是一个可重入读写分离锁,可以认为是在ReentrantLock上的扩展,但是二者没有继承关系,相比于ReentrantLock将读和写分离,读读不互斥,可以提高并发环境下的效率,同样也是基于AQS实现,源码比ReentrantLock要复杂一些,最好能了解AQS源码

ReentrantReadWriteLock内部有Sync,NoNofairSync,FairSync,ReadLock,WriteLock几个内部类,这几个内部类都很关键。类似于ReentrantLock,NoNofairSync和FairSync都是继承了Sync分别实现了公平和非公平两种模式下的策略,Sync继承AQS,如下图所示。
在这里插入图片描述
ReadLock和WriteLock都是实现了Lock和Serializable接口,分别持有一个Sync引用,通过多态将实际的lock和unlock逻辑动态的委托给NoNofairSync和FairSync处理,而ReentrantReadWriteLock则是实现了ReadWriteLock接口,具体如下图所示。
在这里插入图片描述
ReentrantReadWriteLock最重要的不过就是写锁获取,读锁释放,读锁获取,读锁释放四个方法,下面对这四个方法进行详细分析,分析具体逻辑之前,先看看这个类的基本字段。

  1. ReentrantReadWriteLock基本字段
private final ReentrantReadWriteLock.ReadLock readerLock; //读锁引用

private final ReentrantReadWriteLock.WriteLock writerLock; //写锁引用

final Sync sync; //同步器引用

通过读锁和写锁的引用将具体的加锁,释放委托给这两个内部类

  1. Sync的基本字段
        /**
         * 读写锁共用同步状态变量,来同时维护读和写的状态,同步状态变量是一个int型变量
         * 进行按位切割使用,高16位表示读的状态,低16位表示写的状态
         */
        static final int SHARED_SHIFT   = 16; //读锁状态偏移位,偏移16位
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT); //读锁操作的基本单元,读锁状态+1,则状态变量值+SHARED_UNIT
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;//可重入状态的最大值
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//写锁掩码,将同步状态变量和掩码位与就可以得出写锁的状态

还有几个基本字段需要先知道Sync的两个内部类,如下:

		//用来保存一个线程读锁重入的次数
        static final class HoldCounter {
            int count = 0;
            final long tid = getThreadId(Thread.currentThread());
        }     

        /**
         * 继承了ThreadLocal,通过ThreadLocal实现记录线程获取读锁次数的记录
         * key是Thread对象的引用,value是HoldCounter对象
         * HoldCounter对象中保存了获取读锁次数count,和线程tid
         */
        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }
   
		private transient ThreadLocalHoldCounter readHolds;//保存线程获取读锁的次数
      
        /**
         * 最后一个成功获取读锁的线程对应的HoldCounter对象引用,是一个缓存
         * 当获取和释放读锁的时候,需要更新HoldCount,会先检测缓存的线程ID是否和当前线程ID相同
         * 如果相同,就直接通过缓存引用来更新HoldCount值
         * 否则,重新从readHolds中获取HoldCounter对象,同时更新这个缓存
         */
        private transient HoldCounter cachedHoldCounter;
        
        private transient Thread firstReader = null; //第一个获取读锁的线程
        
        private transient int firstReaderHoldCount; //firstReader获取了多少次读锁

以上几个字段主要用于提升效率,如只有一个线程的时候,只会操作firstReaderHoldCount这个整型,以及防止写锁饥饿等

  1. 写锁的获取,因为ReentrantLock获取的就是独占锁,所以写锁的获取和释放基本和ReentrantLock相差不大
        public void lock() {
            sync.acquire(1);
        }

委托给sync对象引用,直接调用了AQS的acquire方法,由AQS的源码可知,这里的调用会导向tryAcquire方法,如下:

        /**
         * 独占锁(写锁)释放
         */
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) { //说明这个锁已经被某个线程持有,可能是读或写
                // (Note: if c != 0 and w == 0 then shared count != 0)
                /**
                 * c!=0 && w==0表示有线程持有共享锁,就算是自己持有共享锁,也会阻塞
                 * 所以ReentrantReadWrLock不支持锁升级
                 * current != getExclusiveOwnerThread()表示,有其他线程持有独占锁
                 * 这两种情况都需要阻塞自己
                 */
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //到这里说明是线程进行了重复获取锁,所以进行可重入处理,直接返回
                setState(c + acquires);
                return true;
            }

            /**
             * 程序执行到这里说明是这个锁是首次被获取,所以要进行并发处理,使用CAS.
             * 1.如果是公平锁,那么writerShouldBlock只允许队列头的线程获取锁
             * 2.如果是非公平锁,不做限制,writerShouldBlock直接返回false
             */
            //
            if (writerShouldBlock() ||
                    !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

这里使用了writerShouldBlock这个方法来限制获取锁的策略,这个方法是Sync提供的Abstract方法,然后由NoNofairSync和FailSync分别实现不同的策略。

FairSync的writerShouldBlock实现:

        /**
         * 对于公平锁来说,如果有前驱(也就是非头结点),都会进行等待,不允许竞争锁
         */
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }

NoNofairSync的writerShouldBlock实现:

        final boolean writerShouldBlock() {
            return false; // 对于非公平锁,没有任何限制
        }
  1. 写锁的释放
        public void unlock() {
            sync.release(1);
        }

同理委托给Sync处理,Sync调用AQS的release方法,会导向tryRelease方法,Sync重写如下:

        /**
         * 独占锁(写锁)获取
         * 与ReentrantLock中的tryRelease方法没有什么差别
         */
        protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively()) //如果当前线程不是持有锁的独占线程,抛出异常
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)//因为可重入,只有当exclusiveCount(nextc) == 0时候,才会真正释放锁
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }
  1. 读锁的获取,读锁的获取比较复杂
        public void lock() {
            sync.acquireShared(1);
        }

同理委托给Sync处理,Sync调用AQS的acquireShared方法,会导向tryAcquireShared方法,Sync重写如下:

        /**
         * 共享锁(写锁)获取
         * @param unused
         * @return
         */
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            /**
             * 已经有其他的线程持有了独占锁,让自己阻塞
             * 但是如果是自己已经持有了独占锁,那么允许自己再获取共享锁
             * ReentrantReadWriteLock的支持锁降级,但是不支持锁升级
             */
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            /**
             * 1.通过公平/非公平的排队规则限制
             * 2.读锁状态未达到可重入状态的最大值
             * 3.CAS设置同步状态成功
             */
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) { //第一次被线程获取
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) { //更新首次获取锁线程HoldCount
                    firstReaderHoldCount++;
                } else { //更新非首次获取锁线程的HoldCount
                    HoldCounter rh = cachedHoldCounter; //先查缓存
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get(); //缓存没有命中,从ThreadLocal中获取
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            //一次获取读锁失败后,尝试循环获取
            return fullTryAcquireShared(current);
        }

这个方法里调用了几个子方法,首先来看readerShouldBlock方法,同样这个也是分别有公平和非公平两种实现

FairSync的readerShouldBlock实现:

        /**
         * 对于公平锁来说,如果有前驱(也就是非头结点),都会进行等待,不允许竞争锁
         */
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }

NoNofairSync的readerShouldBlock实现:

        final boolean readerShouldBlock() {
            /**
             * 由于非公平的竞争,并且读锁可以共享,所以可能会出现源源不断的读
             * 使得写锁永远竞争不到,然后出现饿死的现象
             * 所以通过这个策略,当一个写锁申请线程出现在头结点后面的时候
             * 会立刻阻塞所有还未获取读锁的其他线程,让步给写线程先执行
             */
            return apparentlyFirstQueuedIsExclusive();
        }

在tryAcquireShared中经行了一次快速锁获取,但是由于CAS只能允许一个线程获取锁成功,且读锁是共享的,可能存在其他仍然可以获取锁的线程,所以在函数末尾调用函数fullTryAcquireShared来进行死循环的获取锁,这个函数很关键,代码分析如下:

        final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    /**
                     * 如果是其他线程获取了写锁,那么把当前线程阻塞
                     * 如果是当前线程获取了写锁,不阻塞,否则会造成死锁
                     * 从这里可以看到ReentrantReadWriteLock允许锁降级
                     */
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                } else if (readerShouldBlock()) {
                    /**
                     * 进入这里说明,同步队列的头结点的后继有一个竞争写锁的线程
                     * 所以这里有一个锁让步的操作,即让写锁先获取
                     * 如果是firstReader必然是重入,或者rh.count>0也必然是重入
                     * 对于读锁重入是允许死循环直到获取锁成功的,不然会导致死锁
                     * 但是如果rh.count = 0就说明,这个线程是第一次获取读锁
                     * 为了防止写饥饿,直接将他们重新扔会同步队列,而且这些阻塞不会导致死锁
                     */
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }

  1. 读锁的释放
        public void unlock() {
            sync.releaseShared(1);
        }

同理委托给Sync处理,Sync调用AQS的releaseShared方法,会导向tryReleaseShared方法,Sync重写如下:

        /**
         * 共享锁(读锁)释放
         */
        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) { //更新第一个获取到读锁线程的HoldCount
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else { //更新其他线程的HoldCount
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) { //如果count <= 1就清除这个key,value
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) { //死循环CAS更新state
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

学习完ReentrantReadWriteLock源码,应该了解如何通过AQS实现读写分离,如何实现锁降级,如何防止非公平模式下写线程饿死等这些问题。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值