java基础 - ReentrantReadWriteLock

ReentrantReadWriteLock,可重入的读写锁,一个ReentrantReadWriteLock对象中,包含一个Sync同步器对象、一个ReadLock读锁对象 以及一个WriteLock写锁对象。

Sync、ReadLock、WriteLock都是ReentrantReadWriteLock的内部类,其中Sync继承自AbstractQueuedSynchronizer,所以ReentrantReadWriteLock也是基于AQS框架的实现。

ReentrantReadWriteLock支持公平和非公平模式,其中读锁为共享锁,写锁为互斥锁,他们共用一个AQS同步器。

AQS中只有一个记录状态的整形变量state,ReentrantReadWriteLock中,将其拆分为高16位和低16位来使用,
高16位表示读锁的持有计数,实际持有量 = state >>> 16,即无符号右移16位;
低16位表示写锁的持有计数,实际持有量 = state & (1 << 16) - 1,即1左移16位再减1之后(高位八个0,低位八个1),与state按位与。

ReentrantReadWriteLock构造函数,及提供读锁、写锁的方法如下:

    public ReentrantReadWriteLock() {
        this(false);
    }

    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

接下来以非公平模式为例,分析读写锁的加锁、解锁流程。

读锁(共享锁)的获取

ReadLock#lock()

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

	/*
	实际调用的是AQS的acquireShared方法,其中tryAcquireShared由AQS子类实现,doAcquireShared由AQS自身实现。
	先尝试获取锁,如果失败则进入队列,之后再尝试获取。
	*/
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

tryAcquireShared

        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. If not, try
             *    to grant by CASing state and updating count.
             *    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.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            //如果写锁被持有,并且持有者不是当前线程,则返回-1
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            //如果当前线程不需要阻塞,并且读锁的持有计数小于最大值,并且CAS修改state成功,则表示当前线程已经获得了读锁
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                	/*
                	如果读锁的持有计数为0,则把当前线程设为第一个获取读锁的线程(严格来讲,firstReader是最后一个将共享计数从0改为1的唯一
                	线程,并且一直没有完全释放掉锁)
                	*/
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                	//如果之前读锁已经被持有,并且第一个获取读锁的线程就是当前线程,只改变其持有计数
                    firstReaderHoldCount++;
                } else {
                	/*
                	如果之前读锁已经被持有,且firstReader不是当前线程,那么获取cachedHoldCounter(上一个获取到读锁的线程的持有计数)
                	*/
                    HoldCounter rh = cachedHoldCounter;
                    /*
                    如果rh == null,也就是说cachedHoldCounter还没有缓存(firstReader的计数用的是firstReaderHoldCount,而不是
                    cachedHoldCounter,所以当前线程可能是第二个获取到读锁的线程),
                    或者rh.tid != getThreadId(current),即上一个获取到读锁的线程并不是当前线程;
                    也就是说,如果缓存没有命中,则从当前线程的ThreadLocal变量中获取它的HoldCounter(没有则初始化一个)
                    */
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    /*
                    如果缓存命中,但是在本次获取读锁之前,当前线程已经把读锁全部释放掉了(在tryReleaseShared释放锁的方法中,可能
                    调用readHolds.remove()方法,清除了线程本地ThreadLocal变量),这时需要将readHolds设置一遍。
                    */
                    else if (rh.count == 0)
                        readHolds.set(rh);
                 	//当前线程的读锁计数自增
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

readerShouldBlock

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }
	
	//非公平模式下判断读线程是否需要阻塞,就是判断当前等待队列中头结点的后继结点是否在等待写锁。
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

fullTryAcquireShared
tryAcquireShared方法中描述的仅仅是当前读线程不需要阻塞且CAS拿锁一次成功的场景,而fullTryAcquireShared方法,是全版本的获取读锁方法,考虑了需要阻塞、重入、CAS失败等情况,是对tryAcquireShared的完善和补充。

        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.
             */
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                	//如果写锁被其他线程持有,返回-1,表示拿锁失败
                    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
                    /*
                    这里readerShouldBlock为true,但是没有直接返回-1,就是考虑到有重入的情况。
                    如果firstReader == current,肯定是重入,直接放过去。
                    */
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            /*
                            这里判断是否命中缓存,最终保证ch指向当前线程的读锁计数。
                            如果rh.count == 0,则表示当前线程并非重入,线程本地保存读锁计数也就没什么意义,调用remove移除,然后返回-1;
                            如果rh.count != 0,则表示当前线程为重入,这里不做处理,放过去。
                            */
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
               	//后面类似tryAcquireShared中获取读锁成功后的处理,不同的是,这里如果没有成功会进行自旋。
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    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;
                }
            }
        }

doAcquireShared
如果 tryAcquireShared(包含fullTryAcquireShared)方法获取读锁失败,则进入doAcquireShared方法:

    private void doAcquireShared(int arg) {
    	//添加等待结点,这里通过参数指定为共享模式
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        //整体和互斥模式用到的acquireQueued方法类似,区别在于拿锁用tryAcquireShared方法,以及拿到锁后调用的是setHeadAndPropagate方法。
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

setHeadAndPropagate

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
         
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            /*
            如果当前结点的后继也是共享结点(等待读锁),则调用doReleaseShared唤醒该后继结点。
            这里后继结点可能为null,因为当前结点可能是尾结点,或者后继结点刚入队,当前结点的next指针还没指向它;但这些并不妨碍调用
            doReleaseShared,正如官方注释中说的,这些保守的检查可能会导致不必要的唤醒。
            */
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

先对if判断前的注释做一个解读:

在当前结点获取共享锁成功后,尝试唤醒其后继结点,如果满足以下两个条件:
1、调用者指明了需要传播,也就是入参propagate > 0,表示还有剩余共享资源可以获得;或者被一个之前的操作(比如setHead之前或之后修改了h.waitStatus)记录了需要传播。注意:这里使用 waitStatus < 0,而非直接判断PROPAGATE状态,是因为PROPAGATE有可能会被修改为SIGNAL状态,这是无法精确感知的。
2、当前结点的后继结点在共享模式下等待(比如等待读锁),或者我们压根不知道后继结点,因为某些情况下next指针为null。
这两种检查的保守性可能会导致不必要的唤醒,但只有在有多个同步获取/释放时才会如此,所以大多数情况下现在或很快就需要信号。

这里引入了一个行为叫做传播,意思是当前获取共享锁的线程,同时需要唤醒其后继结点(前提是该后继结点也处于共享模式)。
这种行为不难理解,比如读锁,因为它是共享的,所以等待队列中有一个结点获得了读锁,意味着在资源充足的情况下,后面一连串等待读锁的结点都有资格去获取它。所以一个共享结点获得锁时,会尝试唤醒其后继结点,后继结点获得锁时,再唤醒自己的后继结点,依次传播下去,直到遇到一个排他的结点(等待写锁的线程)。

传播的条件(if条件):propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0

propagate,它是tryAcquireShared方法的返回值。
大于0,表示本次获取共享锁成功,并且后续的获取也可能成功(剩余信号量充足);
等于0,表示本次获取共享锁成功,但是后续的获取不会成功(剩余信号量不足);
小于0,表示本次获取共享锁失败。

propagate > 0,表示剩余资源充足,共享锁的获取是可传播的,直接判断当前结点的后继结点是否为共享模式,然后调用doReleaseShared进行唤醒。
h == null 和 (h = head) == null,目前没有找到对应场景,因为此时队列的结构已经是完整的,不管是原head结点还是当前结点作为head,都不会为null,此处应该是防范未知情况做的兜底。

如果propagate == 0,表示后续资源不足,为什么还要判断两个 h.waitStatus < 0,去尝试唤醒后继结点呢?
因为在并发环境中,当前结点拿到锁之后,进行if判断之前或者过程中,可能有线程释放了共享锁,也就是说仅仅依靠propagate的值来判断是否还有共享资源,以及是否传播,是不准确的。所以我们需要尽可能的去感知共享资源的释放。

共享资源的释放需要用到 releaseShared方法:

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared,释放共享资源,这是具体的锁实现类需要重写方法,AQS框架这里不关注;
doReleaseShared,唤醒结点,这个是AQS的方法,只要某个线程释放共享锁成功,那么就会走到唤醒结点的流程。

doReleaseShared方法源码如下:

    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

这个方法中,判断头结点的 waitStatus 如果为-1,则表示其后继结点正在阻塞,需要唤醒,这是唤醒结点的常规操作。
但如果 waitStatus 是0,则把它修改为 -3,也就是PROPAGATE状态。这个操作的意义,在于引入一个PROPAGATE状态,用来和 -1、0做出区别,表示当前有共享资源释放了,如果哪个结点拿到共享锁的时候读到了这个状态,记得唤醒一下共享的后继结点。这里其实就是为setHeadAndPropagate的 if 判断做铺垫。

下面结合实际并发场景分析 compareAndSetWaitStatus(h, 0, Node.PROPAGATE) 是如何跟 if条件里的 h.waitStatus < 0配合的。
以下面这个模型为例:
在这里插入图片描述
假设当前结点A拿到了共享锁,并且返回的propagate == 0,此时 if条件里的第一个 h.waitStatus < 0,也就是原head结点O的状态,可能有如下几种情况:
1、状态为0,比如:

  • A刚进队列就拿锁成功,没有经过 shouldParkAfterFailedAcquire 方法中将O状态改为-1的过程,O为初始状态0;
  • A已经将O状态改为-1,但是O拿锁成功后发现propagate > 0,调用doReleaseShared方法唤醒了A,并且把自己状态从-1改为0;
  • A已经将O状态改为-1,但是在A执行setHead之前,其他线程释放共享锁,调用doReleaseShared方法将O状态从-1改为0

2、状态为-3,比如:

  • O拿锁成功后发现propagate > 0,调用doReleaseShared方法把自己状态从0改为-3,然后A进队拿锁成功;
  • 上面三种状态为0的场景,如果紧随其后出现了其他线程释放共享锁,都会调用doReleaseShared方法将O状态从0改为-3。

3、状态为-1,比如:

  • A进队列后拿锁失败,在 shouldParkAfterFailedAcquire 方法中将O的状态改为-1,然后A自旋拿锁成功;而在改为-1前,O的状态有可能是初始的0,也有可能发生了doReleaseShared调用被改成了-3,对应了官方注释中的 “PROPAGATE status may transition to SIGNAL”。

综合以上的场景可以说明,当前结点在获取共享锁成功时,原头结点的状态可能是-3、-1、0中的任何一个,而且每种状态都可能经历了其他线程释放共享锁,调用doReleaseShared方法的过程。所以根据 h.waitStatus来判断是否唤醒后继结点,是无法精准匹配到共享锁释放的场景的。

疑问1:为什么这里使用h.waitStatus < 0,而不使用<=0 或判断其他值?
分析:首先,虽然-3、-1、0每种状态的演变过程,都可能包含了共享锁的释放,但是0表示无状态,-1表示阻塞唤醒,它们并不具备传播语义;其次,如果直接判断 h.waitStatus == -3的话,这个状态又是不稳定的,可能在shouldParkAfterFailedAcquire方法中被改为-1;所以只能再放宽范围,使用h.waitStatus < 0。

疑问2:既然这里 h.waitStatus < 0 的条件是宽泛且可能不成立的,那它存在的价值是什么,可不可以不要?
分析:尽管这个条件是宽泛且不够准确的,甚至分析完第二个h.waitStatus < 0之后再回来看,发现即使第一个h.waitStatus < 0条件去掉,第二个也完全能够兜底,但是这里第一个h.waitStatus < 0的意义在于尽可能地响应那些 “在当前结点获取到共享锁并且通过setHead修改头指针之前发生的共享资源释放”,并尝试唤醒其后继结点,这也是对头结点PROPAGATE状态的一种响应,这个条件是完全合理的。

如果第一个h.waitStatus < 0不成立(比如等于0),就会走到第二个h.waitStatus < 0的判断,新的h指示当前结点A。
这时A的状态同样有以下几种情况:
1、状态为0,比如:

  • 后继结点B刚入队,还没有执行到shouldParkAfterFailedAcquire方法中将A状态改为-1的语句;
  • 结点B将A的状态改为-1后,有其他线程释放了共享锁,调用doReleaseShared方法,将A的状态改为0;

2、状态为-3,比如:

  • A的状态本来为0,但有其他线程释放了共享锁,调用doReleaseShared方法,将A的状态改为-3;

3、状态为-1,比如:

  • 后继结点B拿锁失败后,在shouldParkAfterFailedAcquire方法中将A状态改为-1。而在改为-1前,A的状态有可能是初始的0,也有可能发生了doReleaseShared调用被改成了-3。

疑问1:同样的,为什么这里又是h.waitStatus < 0?如果这里再出现h.waitStatus == 0的情况,是否会影响到后继结点的唤醒?
分析:首先这里-3是可用的,但是-3这个状态不稳定,可能被改为-1,所以条件放宽到h.waitStatus < 0;
然后看0这个状态,如果此时
1)后继结点B还没执行到shouldParkAfterFailedAcquire方法中将A状态改为-1的语句,那么有可能B即将/正在执行 tryAcquireShared拿锁,或者执行完shouldParkAfterFailedAcquire方法后再次自旋拿锁,此时是不需要对B进行唤醒的。
2)有其他线程释放了共享锁,调用doReleaseShared方法,将A的状态从-1改为0,那么在doReleaseShared中就会对B结点进行唤醒,也不需要再通过A去唤醒。
所以总结就是第二个h.waitStatus < 0起到了兜底的作用,它可以保证后继结点B一定被唤醒(或者本来就醒着)。

疑问2
第二个h.waitStatus < 0意义是什么,能否不要呢?
分析:该条件的意义在于尽可能地响应那些 “在当前结点获取到共享锁并且通过setHead修改头指针之后发生的共享资源释放”,并尝试唤醒其后继结点。
这个条件如果不要的话,又正好前面的if条件都不满足,那么这里就不会对后继结点进行判断和唤醒,结合此时头结点A的状态:

  • 如果为0,还好,后继结点还没有休眠,会尝试自旋地获取共享锁;
  • 如果为-1,问题也不大,虽然后继结点没有第一时间尝试唤醒,但是后续共享锁释放的时候会调用doReleaseShared,发现是-1,就会改成0,并且唤醒后继结点;
  • 如果为-3,且后继结点处于休眠状态的话,就麻烦了。后继结点只能依赖doReleaseShared方法来唤醒,但是doReleaseShared中只会针对状态为-1的头结点,进行后继结点的唤醒,-3是不处理的,也就是说没有线程会再去唤醒后继结点了。

所以总体上来看,两个h.waitStatus < 0条件是配合doReleaseShared方法中设置的PROPAGATE状态来发挥作用的。这两个条件的覆盖面不同,但目的都是在 propagate == 0的情况下,也尽可能地尝试唤醒共享模式的后继结点,提高AQS的使用效率。

读锁(共享锁)的释放

ReadLock#unlock()

        public void unlock() {
            sync.releaseShared(1);
        }

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared是AQS子类需要实现的方法,而doReleaseShared由AQS框架实现;
doReleaseShared方法之前已经分析过,主要用来为队列头结点设置传播状态,以及唤醒头结点的后继结点,这里主要看下tryReleaseShared方法。

tryReleaseShared

        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            //先判断firstReader缓存,如果命中则更新缓存
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                //再判断cachedHoldCounter缓存,如果未命中,则从线程本地的readHolds中读取
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                //持有数量小于等于1的话,再释放就没有了,ThreadLocal变量readHolds也就没有保留价值了,下次用的时候再initialValue就行。
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                //线程本地持有计数自减
                --rh.count;
            }
            //CAS自旋保证修改state值成功,这里规定只有读锁全部被释放掉,没有任何线程持有时,才返回true。
            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;
            }
        }

写锁(互斥锁)的获取

WriteLock#lock()

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

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

tryAcquire

        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);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                /*
                c != 0仅表示锁被占用,什么锁不知道。w == 0表示写锁没被占用,如果它成立则说明读锁被占用。
                这里if条件比较绕,总的来说就是:
                在有锁被占用的前提下,当且仅当写锁被当前线程占用时,该线程才有资格获取写锁(重入),其他情况一律不行。
                */
                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;
            }
            //这里走到的是另一个大前提:任何锁都没有被占用
            //非公平模式下,writerShouldBlock恒定返回false,所以直接CAS抢锁,这里如果失败了,就会回到acquire方法中进队列。
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

后面的 acquireQueued、addWaiter方法都是AQS的实现,在 ReentrantLock中分析过,此处不再关注。

写锁(互斥锁)的释放

WriteLock#unlock()

    public void unlock() {
        sync.release(1);
    }

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

写锁的释放和获取一样,都是直接调用了AQS的模板实现。

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;
        }

公平模式和非公平模式的差异

公平与非公平的区别主要在于执行拿锁操作前,是否需要判断队列中已经有线程在等待。
ReentrantReadWriteLock中,该差异体现在 readerShouldBlock 和 writerShouldBlock两个方法上,这两个方法分别在 tryAcquireShared 和 tryAcquire方法中被调用。

非公平模式下的实现:

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }

公平模式下的实现:

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

hasQueuedPredecessors是AQS的框架实现,ReentrantLock中也有分析过。

最后总结下读锁和写锁的互斥性:
1、写锁被持有的情况下,只有持有写锁的线程自己可以申请读锁(也叫作锁降级),其他线程不能申请读锁;
2、读锁被持有的情况下,持有读锁的线程必须保证获取读锁之前先持有了写锁,才能再次申请写锁(重入),单纯的只持有读锁是不能申请写锁的(不支持升级),而其他线程不能申请写锁。

锁降级的合理性:
在一个线程独占写锁进行写数据的时候,其他线程是无法读数据的,但是要保证写后的数据对线程自己可见,就得让自己申请到读锁,毕竟他读自己写的数据,读取时机是可控制的,也不会影响到其他线程,所以锁降级是合理的。反之,如果不给他读锁,那么写完数据之后,一旦写锁被别的线程抢到,那自己写完的数据连自己都读不到了,这就不太合理了。

锁升级的不合理性:
当写锁没被独占的时候,一个线程在读数据,由于读锁是共享的,同时可能有很多其他线程也在读,这个时候其中一个线程来申请写锁,想要修改数据,显然是不合理的,一旦让他修改,那么多在读的线程,让他们挂起吗?显然不行,让他们接着读?会读到不一致的结果,也不行。所以锁升级是不合理的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值