源码 预防针: 源码篇 必然会有很多代码,期望大家不要有畏难情绪,虽然我也是看到一大串代码就头疼。 我贴代码仅仅为了便利我的文字解答,源码仅仅辅佐我文字讲解,所以大家尽量重视我的文字就好啦。

模版办法

好了到这儿就要开端进入主题了,依照之前的过程,咱们要开端从 AQS 的模版办法开端介绍 ReentrantReadWriteLock

WriteLock:写锁

    public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        /** 调用父类的 acquire 办法 */
        public void lock() {
            sync.acquire(1);
        }
    }

acquire:写锁的模版办法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
        }

这儿再贴一下 AQS 的模版办法,然后咱们需求重视的就是 ReentrantReadWriteLock 实现的 tryAcquire 办法。

tryAcquire:写锁的加锁逻辑

WriteLock 中心的加锁逻辑。

    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.)
         * 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 代表着我并不是初次来获取锁,之前就现已存在了读写锁
        // 假如我是初次来获取锁的线程,我能够很高兴的直接经过下面的 cas 去进行加锁
        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;
        }
        if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
            return false;
        setExclusiveOwnerThread(current);
        return true;
    }

假如你把源码上面的注开释到翻译软件上面看,就会得到这坨东西:

源码级丨AQS 同享锁 写锁中心加锁逻辑

不知道对你有没有协助,反正对我是没有一点协助。

关于 tryAcquire 相信你看过我之前对 ReentrantLock 的讲解应该基本是没问题,需求特别注意的只有他多了一个 writerShouldBlock 办法,而这个办法就是 写锁公正性 的表现。

writerShouldBlock:写锁的公正性表现

咱们再来看看 writerShouldBlock 这个办法

    /** 公正锁 */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }
    /** 非公正锁 */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
    }
  • 公正锁:就很简单,直接让交给 CLH 锁去处理,关于 CLH 锁的介绍在上一篇文章有解释:TODO:。

  • 非公正锁:是直接返回 False一直答应插队, 这也是 排他锁 的特性表现。

这边贴一个我用更直白的话语注释好的,便利读者了解具体的过程

    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        int c = getState();
        int w = exclusiveCount(c);
        // c != 0 代表着我并不是初次来获取锁,之前就现已存在了读锁或写锁了
        // 假如我是初次来获取锁的线程,我能够很高兴的直接经过下面的 cas 去进行加锁
        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");
            // 都满足了合法性检查,那么就能够直接更新 state 代表获取锁成功了
            // Reentrant acquire
            setState(c + acquires);
            return true;
        }
        // 来到这儿还需求判别 公正性
        // 公正锁:假如有前置节点,代表需求排队,就交给 CLH 去排队
        // 非公正锁:总是答应插队,直接经过 CAS 插队
        if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
            return false;
        setExclusiveOwnerThread(current);
        return true;
    }

tryRelease:写锁的开释锁逻辑

    protected final boolean tryRelease(int releases) {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases;
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }

先判别是否是 锁持有线程,然后再依据 锁重入次数 判别 是否完好开释锁

写锁获取锁流程

源码级丨AQS 同享锁 写锁中心加锁逻辑

  • 判别是否是 之前是否有持有锁 来获取写锁:
    • 假如是:进行 合法性判别
      • 排他性判别(假如现已有同享锁,则会失利,由于排他性质)。
      • 可重入性判别。
      • 最大次数判别。
    • 假如不是:则还需求依据 公正性 进行 CAS 获取。
      • 公正锁:就扔去 CLH 行列排队获取
      • 非公正锁:直接插队进行 CAS 获取锁,不行再丢去 CLH 行列排队。

总结

  • 写锁的互斥性:在获取锁的时分,会先依据 state 判别是否持有锁,这儿包含了 排他性 的判别,由于写锁和所有锁都互斥,所以只需求判别 state ≠ 0,代表这儿发生互斥了而且假如不是锁持有的线程,那么就会获取锁失利。

  • 写锁的公正性 :表现在 writerShouldBlock 这个办法中,公正锁 直接丢 CLH 行列乖乖排队,非公正锁 能够先插队进行 CAS 尝试获取锁,获取失利再丢去 CLH

源码级丨AQS 同享锁 写锁中心加锁逻辑

来都来了,点个赞,留个言再走吧彦祖,这对我来说非常重要!