【锁】ReadWriteLock之读写锁在获取锁与释放锁分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

文章首发于:clawhub.club


由ReentrantReadWriteLock源码可知,读锁与写锁共同使用了一个Sync同步器,即使用了同一个同步队列。

获取锁

获取读锁

     public void lock() {
                sync.acquireShared(1);
            }

直接调用了同步器的共享锁。AQS中的acquireShared方法会回调Sync的tryAcquireShared(int unused)方法。

     public final void acquireShared(int arg) {
            if (tryAcquireShared(arg) < 0)
                doAcquireShared(arg);
        }

tryAcquireShared方法相对来说就比较复杂了,连蒙带猜写的注释:

     /**
             * 共享模式获取锁,当前锁可以被多个线程所持有,state代表当前锁的持有线程数量
             * @param unused 获取锁的数量
             * @return
             */
            protected final int tryAcquireShared(int unused) {
                /*
                 * Walkthrough:
                 * 1. If write lock held by another thread, fail.
                 * 如果另一个线程持有写锁,则失败。
                 * 2. Otherwise, this thread is eligible for
                 *    lock wrt state, so ask if it should block
                 *    because of queue policy.
                 * 否则,这个线程就符合锁定wrt状态的条件,所以请询问它是否应该由于队列策略而阻塞。
                 *    If not, try to grant by CASing state and updating count.
                 * 如果不是,尝试用CAS更新数量。
                 *    Note that step does not check for reentrant
                 *    acquires, which is postponed to full version
                 *    to avoid having to check hold count in
                 *    the more typical non-reentrant case.
                 * 注意,那一步不检查可重入获取,它被推迟到完整版本,以避免在更典型的不可重入情况下检查hold count
                 * 3. If step 2 fails either because thread
                 *    apparently not eligible or CAS fails or count
                 *    saturated, chain to version with full retry loop.
                 * 如果第2步失败,要么是因为线程显然不符合条件,要么是因为CAS失败或计数饱和,则使用完整的重试循环链接到版本。
                 */
                //获取当前的线程
                Thread current = Thread.currentThread();
                //获取当前锁的状态
                int c = getState();
                //c中表示独占持有的数量不为0:表示有写锁
                //当前线程并不是获取写锁的线程
                if (exclusiveCount(c) != 0 &&
                        getExclusiveOwnerThread() != current)
                    //获取锁失败
                    return -1;
                //c中表示的共享持有的数量
                int r = sharedCount(c);
                //查看读锁是否应该阻塞
                //读锁的数量是否超过限制
                //尝试修改锁状态值是否成功
                if (!readerShouldBlock() &&
                        r < MAX_COUNT &&
                        compareAndSetState(c, c + SHARED_UNIT)) {
                    //读锁不阻塞且读锁数量不超过最大值,并且获取读锁成功

                    //如果r=0
                    if (r == 0) {
                        //当前线程为第一个读者
                        firstReader = current;
                        //读锁持有数量为1
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        //如果发现当前线程就是第一个读者,那就将锁持有数量+1
                        firstReaderHoldCount++;
                    } else {
                        //不是第一个读者,获取读锁持有线程计数器
                        HoldCounter rh = cachedHoldCounter;
                        //如果还没有计数器,或者当前线程的线程Id与计数器的不同
                        if (rh == null || rh.tid != getThreadId(current))
                            //再获取一个计数器,readHolds为ThreadLocal的子类,线程共享
                            cachedHoldCounter = rh = readHolds.get();
                        //如果计数器的持有值为0
                        else if (rh.count == 0)
                            //将计数器保存到ThreadLocal
                            readHolds.set(rh);
                        //计数器的值+1
                        rh.count++;
                    }
                    //获取锁成功
                    return 1;
                }
                //如果不满足条件:读锁不阻塞且读锁数量不超过最大值,并且获取读锁成功
                //则执行下面的方法
                return fullTryAcquireShared(current);
            }

fullTryAcquireShared方法和tryAcquireShared方法冗余度确实挺高的,只有满足以下几个条件,才会进入完整版本获取锁方法:

  • 没有写锁
  • 读阻塞readerShouldBlock方法返回true
  • 读锁持有线程数量大于等于MAX_COUNT
  • 尝试获取读锁失败
       /**
             * Full version of acquire for reads, that handles CAS misses
             * and reentrant reads not dealt with in tryAcquireShared.
             * 获取读锁更完整版本,处理tryAcquireShared中没有解决的CAS misses和可重入读
             */
            final int fullTryAcquireShared(Thread current) {
                /*
                 * This code is in part redundant with that in
                 * tryAcquireShared but is simpler overall by not
                 * complicating tryAcquireShared with interactions between
                 * retries and lazily reading hold counts.
                 * 这段代码与tryAcquireShared中的代码在一定程度上是冗余的,但总的来说更简单,
                 * 因为它没有使tryAcquireShared在重试和延迟读取hold count之间的交互复杂化。
                 */
                HoldCounter rh = null;
                //循环
                for (; ; ) {
                    //获取当前锁状态
                    int c = getState();
                    //c中表示独占持有的数量不为0,并且当前线程并不是获取写锁的线程。
                    if (exclusiveCount(c) != 0) {
                        if (getExclusiveOwnerThread() != current)
                            return -1;
                        // else we hold the exclusive lock; blocking here
                        // would cause deadlock.
                        //查看是否阻塞读锁
                    } else if (readerShouldBlock()) {
                        // Make sure we're not acquiring read lock reentrantly
                        // 确保我们没有重入读锁
                        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)
                                        //ThreadLocal移除当前线程
                                        readHolds.remove();
                                }
                            }
                            //读锁数量为0
                            if (rh.count == 0)
                                return -1;
                        }
                    }
                    //如果读锁数量等罪最大值,抛出Error
                    if (sharedCount(c) == MAX_COUNT)
                        throw new Error("Maximum lock count exceeded");
                    //CAS尝试获取读锁
                    if (compareAndSetState(c, c + SHARED_UNIT)) {
                        //如果读锁数量为0
                        if (sharedCount(c) == 0) {
                            //设置第一个读者
                            firstReader = current;
                            firstReaderHoldCount = 1;
                        } else if (firstReader == current) {
                            //第一个读者重入
                            firstReaderHoldCount++;
                        } else {
                            //不是第一个读者,使用计数器
                            if (rh == null)
                                rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current))
                                rh = readHolds.get();
                            else if (rh.count == 0)
                                readHolds.set(rh);
                            rh.count++;
                            cachedHoldCounter = rh; // cache for release
                        }
                        return 1;
                    }
                }
            }

获取写锁

直接看代码注释即可。

      /**
             * 独占模式获取锁
             * @param acquires 获取的数量
             * @return
             */
            protected final boolean tryAcquire(int acquires) {
                /*
                 * Walkthrough:
                 * 1. If read count nonzero or write count nonzero
                 *    and owner is a different thread, fail.
                 * 如果读计数非零或写计数非零且所有者是另一个线程,则失败。
                 * 2. If count would saturate, fail. (This can only
                 *    happen if count is already nonzero.)
                 * 如果计数饱和,则失败。(只有当count已经非零时才会发生这种情况。)
                 * 3. Otherwise, this thread is eligible for lock if
                 *    it is either a reentrant acquire or
                 *    queue policy allows it. If so, update state
                 *    and set owner.
                 * 否则,如果这个线程是可重入获取或队列策略允许,那么它就有资格获得锁。
                 * 如果是,则更新状态并设置所有者。
                 */
                //获取当前线程
                Thread current = Thread.currentThread();
                //获取当前锁状态
                int c = getState();
                //获取独占锁持有数量
                int w = exclusiveCount(c);
                //c!=0 表示有线程持有锁
                if (c != 0) {
                    // (Note: if c != 0 and w == 0 then shared count != 0)
                    //没有写锁线程(那一定有读锁线程),或者当前线程不是持有互斥锁线程
                    if (w == 0 || current != getExclusiveOwnerThread())
                        //获取锁失败
                        return false;
                    //如果写锁超限,抛出错误
                    if (w + exclusiveCount(acquires) > MAX_COUNT)
                        throw new Error("Maximum lock count exceeded");
                    // Reentrant acquire
                    //能走到这一步,肯定是锁重入了
                    setState(c + acquires);
                    return true;
                }
                //如果c=0且(写阻塞或者CAS获取锁失败)
                if (writerShouldBlock() ||
                        !compareAndSetState(c, c + acquires))
                    return false;
                //c=0,写不阻塞,CAS获取锁成功,设置独占线程标志位
                setExclusiveOwnerThread(current);
                return true;
            }

释放锁

释放读锁

     /**
             * 共享模式释放锁
             * @param unused
             * @return
             */
            protected final boolean tryReleaseShared(int unused) {
                //获取当前线程
                Thread current = Thread.currentThread();
                //如果当前线程是第一个读者
                if (firstReader == current) {
                    // assert firstReaderHoldCount > 0;
                    //如果此线程重入次数为1,则制空第一读者标志位
                    if (firstReaderHoldCount == 1)
                        firstReader = null;
                    else
                        //重入次数减1
                        firstReaderHoldCount--;
                } else {
                    //如果当前线程不是第一读者,去要从ThreadLocal中获取计数器,并做减一操作
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        rh = readHolds.get();
                    int count = rh.count;
                    if (count <= 1) {
                        readHolds.remove();
                        if (count <= 0)
                            throw unmatchedUnlockException();
                    }
                    --rh.count;
                }
                //循环加CAS套路,释放锁
                for (; ; ) {
                    int c = getState();
                    int nextc = c - SHARED_UNIT;
                    if (compareAndSetState(c, nextc))
                        // Releasing the read lock has no effect on readers,
                        // but it may allow waiting writers to proceed if
                        // both read and write locks are now free.
                        return nextc == 0;
                }
            }

            private IllegalMonitorStateException unmatchedUnlockException() {
                return new IllegalMonitorStateException(
                        "attempt to unlock read lock, not locked by current thread");
            }

释放写锁

          /*
             * Note that tryRelease and tryAcquire can be called by
             * Conditions. So it is possible that their arguments contain
             * both read and write holds that are all released during a
             * condition wait and re-established in tryAcquire.
             * 注意,tryRelease和tryacquisition可以根据条件调用。
             * 因此,它们的参数可能同时包含读和写持有,
             * 这些持有都在一个条件等待期间释放,并在tryAcquire中重新建立。
             */
            protected final boolean tryRelease(int releases) {
                //判断当前线程是不是持有读锁的线程
                if (!isHeldExclusively())
                    throw new IllegalMonitorStateException();
                //释放锁的次数
                int nextc = getState() - releases;
                //如果持有写锁的数量为0了,即当前没有写锁
                boolean free = exclusiveCount(nextc) == 0;
                if (free)
                    //将互斥锁标志位置为null
                    setExclusiveOwnerThread(null);
                //设置线程
                setState(nextc);
                return free;
            }

从获取锁与释放锁的源码中可以看出几个比较重要的属性:

  • firstReader和firstReaderHoldCount
    使用这两个属性的目的应该是优化性能,因为读锁重入或者读锁释放的时候可以直接操作,而不必去用ThreadLocal。
  • cachedHoldCounter和readHolds
    保存了读锁持有的非第一读者的其他线程与其持有的重入数量。
  • exclusiveOwnerThread
    存储独占模式同步的当前所有者。

来源:https://www.jianshu.com/p/347ea7f881f8

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【锁】ReadWriteLock之读写锁在获取锁与释放锁分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏