AQS的应用-锁

在前三篇文章中,我们已经考察了同步队列与条件队列的创建,入队,出队,取消,与转移过程;这一篇文章,我们来考察AQS的应用;

ReentrantLock

可重入锁与AQS都位于java.util.concurrent.locks包下,可以说是AQS最近的应用;
ReentrantLock类结构图

如何创建一个重入锁对象

Sync是AQS的子类,同时又是FairSync与NonfairSync的父类,我们从创建一个重入锁对象来开始分析;
ReentrantLock提供两个构造器,默认的无参构造器实例化一个非公平的同步器;有参构造器,根据传入的值构造同步器;

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

如何实现锁定

是如何实现锁的?AQS的state是共享变量;当state的值=0时,表示资源未被占用;state>0时表示资源已被占用,实现锁定效果;

/**
     * Acquires the lock.
     *
     * <p>Acquires the lock if it is not held by another thread and returns
     * 获取锁如果不被另一个线程占用则快速返回
     * immediately, setting the lock hold count to one.
     *并设置锁的占有次数=1
     * <p>If the current thread already holds the lock then the hold
     *  如果当前线程已经持有了锁,则持有锁的次数+1并立即返回(重入)
     * count is incremented by one and the method returns immediately.
     *
     * <p>If the lock is held by another thread then the
     * 如果锁被其余线程持有
     * current thread becomes disabled for thread scheduling
     * 当前线程不可被线程调度
     * purposes and lies dormant until the lock has been acquired,
     * 并且处于休眠直到锁被获取到
     * at which time the lock hold count is set to one.
     * 在这个时候,锁的持有数量被设置为1
     */
    public void lock() {
        sync.lock();
    }

根据方法的签名与注释,我们知道这个锁不响应中断。
继续追踪代码,有公平锁与非公平锁两种实现;

公平锁与非公平锁的区别

公平锁实现

        final void lock() {
            acquire(1);
        }

非公平锁实现

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

公平与非公平区别点1:
在于在与非公平锁在常规的获取资源方法之前,优先cas设置资源从0->1,如果失败再进行常规获取资源操作。

独占,与重入

acquire是一个模板方法,需要调用子类实现的tryAcquire方法;

    /**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * 独占模式下获取,忽略中断。
     * by invoking at least once {@link #tryAcquire},
     * 通过至少调用一次tryAcquire实现
     * returning on success.  Otherwise the thread is queued, possibly
     * 当成功时返回。不然线程入队,
     * repeatedly blocking and unblocking, invoking {@link
     * 可能重复的阻塞与非阻赛,
     * #tryAcquire} until success.  This method can be used
     * 知道获取资源成功
     * to implement method {@link Lock#lock}.
     * 这个方法可以用来实现锁定
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
    	// 尝试获取资源失败,成功,则返回
    	// 否则,入队;直到在队列中获取到资源后才可以返回;如果有发生中断,则设置中断信息
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

acquireQueued我们研究过,我们现在来研究 tryAcquire 方法;
公平实现

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * 尝试获取的公平版本。除了递归调用或者没有竞争,或者自己是第一个才允许访问资源
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 资源为0表示未被锁定
            if (c == 0) {
            	// 当前没有其它队列中的线程则尝试锁定资源,锁定成功则返回
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    // 设置独占
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 若资源已经被锁定,判断是不是同一个线程
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                // 重入次数,设置state的数量增加,表示重入次数
                setState(nextc);
                return true;
            }
            // 资源为0时自己无法竞争或者竞争失败,或者资源已被锁定,则直接返回false;
            return false;
        }
    }

非公平实现

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * 实施非公平尝试锁。tryLock也是非公平的
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                	// 设置独占
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

公平与非公平区别点2:
与公平锁的区别在于,当检测到资源为0时,直接尝试CAS锁定,不检测队列中是否有其他的排队线程;

总结

我们已经分析了重入锁如何实现锁,实现重入,以及考察了公平与非公平的区别;区别在于,当调用lock方法时,公平锁会先检查当前资源是否可以占有,再检查队列中是否有别的节点在,或者自己是否位于队首,才去CAS锁;而非公平锁,则是直接尝试cas锁定资源,cas失败后检测到资源还未占用,再去cas锁定一次;

如何释放锁

    /**
     * Attempts to release this lock.
     * 尝试释放锁
     * <p>If the current thread is the holder of this lock then the hold
     * 如果当前线程持有锁则持有数量-1;
     * count is decremented.  If the hold count is now zero then the lock
     * 如果锁持有数量为0,则锁释放
     * is released.  If the current thread is not the holder of this
     * 非持有锁线程无法释放锁,抛出非法管程状态异常
     * lock then {@link IllegalMonitorStateException} is thrown.
     *
     * @throws IllegalMonitorStateException if the current thread does not
     *         hold this lock
     */
    public void unlock() {
        sync.release(1);
    }
	
    /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * 独占模式下释放
     * more threads if {@link #tryRelease} returns true.
     * 调用tryRelease解锁一个或多个线程
     * This method can be used to implement method {@link Lock#unlock}.
     * 该方法可以被用来实现解锁
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
    	// 子类实现的tryRlease方法,释放资源
        if (tryRelease(arg)) {
        	// 获取同步队列头节点
            Node h = head;
            // 如果头节点不为空,且头节点状态不为0,因为尾插法插入同步队列后,都会将前置节点置为-1,自己才会park;为0表示没有后继节点
            if (h != null && h.waitStatus != 0)
                // 唤醒后继节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
// 不区分公平或者非公平,尝试释释放资源
protected final boolean tryRelease(int releases) {
			// 持有锁的数量-释放的数量
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 如果完全释放完毕,则表示锁被解锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            // 重置锁
            setState(c);
            return free;
        }
独占模式下,唤醒后继者

在释放资源成功后,检查头节点的状态是否为非0,是非0则唤醒后继者;

何时头节点为0

在这里,我们考察一下,何种场景下头节点为0;

  • 1,队列创建时,头部节点状态默认为0;入队的节点立刻获取到资源,将自己设置为头节点;当没有额外的资源入队时,此时头节点为0;
  • 2,剩余的任何调用acquire的方式,在阻塞自己之前都会设置头节点为-1;故任何调用acquire方法的入队阻塞了的节点,都会使得头部节点为-1;
  • 3,调用acquire时入队,但阻塞自己之前发生错误,尚未设置为-1,此时头节点可能时0;在取消获取方法中cancelAcquire,我们分析过,三种场景,自己是尾节点,那么头节点将会称为尾节点,符合1;自己的前置节点是头节点,再唤醒自己节点的后续节点,当唤醒自己的后继节点时;如果有可以唤醒的后继节点,则会唤醒它,没有的话头节点是0,符合1;唤醒后的节点获取资源失败,会再次尝试更改头节点为-1,才会park,符合2;最后一种场景,即自己不是尾,也不是头节点的下一个节点,则会设置头节点的状态为-1;设置失败则会park后继节点;总结下来,就是取消的场景,也会保证只要有合理的后继节点,头节点就是-1,否则才为0;
  • 4,从条件队列同步入队;正常唤醒的 transferForSignal方法在入队后,会尝试设置前置节点状态为-1,设置失败则会唤醒该线程,而该线程阻塞在await方法处,线程被唤醒后发现自己无法获取资源又会在沉睡前设置前置线程为-1;唤醒成功,则头节点为当前节点,即状态为-2;小于0;
  • 5,最后一种情况就是条件队列取消等待后进入到同步队列,transferAfterCancelledWait;如果未能将节点状态改为0,则会通过signal方法入队;而如果成功修改为0;则会尝试从队列中获取资源 acquireQueued,成功则会将自己置为头节点;失败则会将前置节点状态为-1;实际上只要自己成功入队,且后续节点还有节点,就一定不会让头节点为0
    总结下来,只要头节点还有后续节点,头节点就为-1或者-2;而不会为0;为0的时候,都是没有后续节点了;
独占模式唤醒后继者源码分析
/**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * 如果当前节点的状态为负,尝试将它置为0
         * to clear in anticipation of signalling.  It is OK if this
         * 当置为0失败时,表示其余的线程已经将它更该,比如改为-1
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * 通常情况下,只需要唤醒后继节点;当后继节点取消或者是看起来是null
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * 后续遍历直到找到一个非取消的节点;
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

关于从后向前遍历的原因,参阅AQS后续遍历找寻继任者

总结

我们分析了AQS作为独占,可重入锁,可选公平与非公平是怎么实现与解锁的,还有可中断锁我们没有分析,是因为可中断锁只是AQS的实现,在 acquire 方法中将中断信号当作异常抛出;另外,还有一些实用的方法,我们没有分析,比如判断锁是否被当前线程占有,当前锁的排队情况等;
总的来说,ReentrantLock也是利用管程思想实现了的AQS锁。

ReentrantReadWriteLock

读写锁简介

可重入读写锁在重入锁基础上升级,在读多写少等场景下,有更好的并发性能;从预期上,我们希望读读之间可以并发,写的时候不允许读。至于读的时候是否允许写,ReentrantReadWriteLock 是不允许读写并发,为了防止脏读。读的时候不允许写,但读读之间共享,可能会导致写线程无限等待,造成饥饿。我们就带着这个问题,去寻找是否有对应的解决方案,来分析可重入读写锁。

读写锁类结构

类概况
在上述类结构中,我们看到ReentrantReadWriteLock 实现了 ReadWriteLock 接口,内部有ReadLock与WriteLock两个类作为具体的读写锁。还有我们熟悉的的三个子类,Sync,FairSync,NonfairSync;我们先从熟悉的公平与非公平的可选同步器开始。

公平与非公平同步器
	      /*
         * Acquires and releases use the same code for fair and
         * 公平锁与非公平锁,获取与释放使用相同的代码
         * nonfair locks, but differ in whether/how they allow barging
         * 不同之处在于当队列非空时它们是否/如何插入
         * when queues are non-empty.
         */
    /**
     * Fair version of Sync
     * 公平版本的同步器
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }
    /**
     * Nonfair version of Sync
     * 非公平版本的同步器
     */
    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();
        }
    }
     /**
     * Returns {@code true} if the apparent first queued thread, if one
     * 如果队首元素存在且时独占模式返回true
     * exists, is waiting in exclusive mode.  If this method returns
     * 如果这个方法返回true
     * {@code true}, and the current thread is attempting to acquire in
     * 且当前线程在共享模式下获取
     * shared mode (that is, this method is invoked from {@link
     * #tryAcquireShared}) then it is guaranteed that the current thread
     * 那么保证当前线程不是第一个队列中的线程。
     * is not the first queued thread.  Used only as a heuristic in
     * 该方法是仅在读写锁中作为试探法使用
     * ReentrantReadWriteLock.
     */
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

可以看出,公平与非公平的区别在于当队列中有其他元素时,公平同步器总是阻塞自己,而非公平的写锁则是不阻塞,而非公平的读锁则是判断如果队首元素是否是写等待线程。
在这里我们可以看出,doug lee 大神在非公平的读锁时,为了减少阻塞的队列。
我们直到非公平锁的效率高于公平锁,但是非公平写锁可能会产生饥饿,故对非公平读锁加了一点点限制,就是当队首为写线程时,则阻塞非公平读锁。

读锁与写锁的实现
如何构造一个读锁/写锁

ReentrantReadWriteLock 的构造器,在这个构造器中根据是否指定公平锁,创建一个对应的同步器,并分别去构造对应的读锁与写锁。

    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * the given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
		
		// 同步器初始化方案
	    Sync() {
            readHolds = new ThreadLocalHoldCounter();
            // 此时state为0,对volatile字段进行读写操作,禁止重排序并保证可见性
            setState(getState()); // ensures visibility of readHolds
        }

        /**
         * Constructor for use by subclasses
         * 构造一个读锁
         * @param lock the outer lock object
         * @throws NullPointerException if the lock is null
         */
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

		/**
         * Constructor for use by subclasses
         * 构造一个写锁
         * @param lock the outer lock object
         * @throws NullPointerException if the lock is null
         */
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
读写锁如何实现锁定与解锁的背后原理

我们已经直到AQS提供独占模式的获取与释放,而且写锁就是独占模式,自然而然的写锁的原理就是AQS的独占模式下的获取与释放;而读锁是共享的,自然而然读锁的原理就是AQS的共享模式下的获取与释放。
WriteLock

		public void lock() {
            sync.acquire(1);
        }
        public boolean tryLock( ) {
            return sync.tryWriteLock();
        }
        public void unlock() {
            sync.release(1);
        }
        /**
         * Performs tryLock for write, enabling barging in both modes.
         * 写锁的tryLock,在两种模式中执行插入(公平与非公平)
         * This is identical in effect to tryAcquire except for lack
         * 这与tryAcquire效果相同只是缺少检查 writerShouldBlock
         * of calls to writerShouldBlock.
         */
		final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            // 共有的资源,低16位被用来标识写锁的个数,因为写锁是独占锁,只要写锁自增+1就代表重入
            int c = getState();
            // 资源被占用
            if (c != 0) {
            	// 写锁的个数
                int w = exclusiveCount(c);
                // 资源被占用,但是写锁为0,表示有读锁,有读锁时不能获取写锁,故直接返回失败
                // 资源被占用,且写锁不为0,检查写锁的持有线程是不是当前线程,不是当前线程则返回失败
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                // 资源被占用,且时当前线程占用的,检查是否达到写锁上限个数,1<<16-1的个数
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            // 资源未被占用,则尝试直接占用,占用失败则直接返回失败
            if (!compareAndSetState(c, c + 1))
                return false;
            // 再次设置当前线程为独占锁拥有线程
            setExclusiveOwnerThread(current);
            return true;
        }

ReadLock

		public void lock() {
            sync.acquireShared(1);
        }
        public boolean tryLock() {
            return sync.tryReadLock();
        }
        public void unlock() {
            sync.releaseShared(1);
        }
        /**
         * Performs tryLock for read, enabling barging in both modes.
         * 读锁的tryLock,在两种模式下都允许插入(公平与非公平)
         * This is identical in effect to tryAcquireShared except for
         * 与 tryAcquireShared 效果一致,仅是缺少readerShouldBlock检查
         * lack of calls to readerShouldBlock.
         */
        final boolean tryReadLock() {
            Thread current = Thread.currentThread();
            // 循环获取确切结果
            for (;;) {
           		// 产看资源个数
                int c = getState();
                // 独占锁不为0且独占线程不是当前线程,则直接失败;写读不并发
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return false;
                 // 读锁数量
                int r = sharedCount(c);
                if (r == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 将锁定的资源个数设为c+1<<16,因为c的高16位标识读锁的个数
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                	// 如果读锁数量为0
                    if (r == 0) {
                    	// 当前线程赋值第一个读锁线程
                        firstReader = current;
                        //第一个读锁线程持有读锁数量为1 实现重入
                        firstReaderHoldCount = 1;
                        // 读锁数量不为1,且当前线程为第一个读锁线程
                    } else if (firstReader == current) {
                    	// 第一个读锁线程持有锁数量+1
                        firstReaderHoldCount++;
                    } else {
                    	// 当前线程不是第一个读锁线程,则记录当前线程持有读锁的数量,实现重入
                        HoldCounter rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            cachedHoldCounter = rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                    }
                    return true;
                }
            }
        }
读写锁如何实现重入计数的

在上一节的tryWriteLock与tryReadLock中,我们已经看到过相关关于重入计数。
ReentrantReadWriteLock 将 AQS的int 类型 state 字段,分为高16位 unsined short标识读锁的数量,而低16位表示写锁的数量;
由于写锁是独占锁,故写锁的重入只需要将state字段进行加减即可;
读锁是共享锁,不能再简单的对state字段进行加减实现。对于第一个读锁线程,用 Thread firstReader 表示这个线程,用 firstReaderHoldCount 表示这个线程重入的次数;对于其余的读线程,使用 HoldCounter 记录线程id 与线程的重入次数,存储在 hreadLocalHoldCounter readHolds 中;这样总的读锁数量在 state的高16位标识,而每个线程的持有锁数量在每个线程自己的houldCounter中。

读锁与写锁阻塞与释放

我们已经了解过公平与非公平,读锁与写锁怎么实现的(基于 直接返回结果的 tryReandLock和tryWriteLock),以及如何实现重入的。现在我们完整分析Sync是如何读锁与写锁的。

写锁获取与 tryAcquire

我们已经直到acquire是模板方法,直接调用子类的tryAcquire方法失败后则尝试入队以及再在queuedAcquire方法中tryAcquire实现获取资源或park;

		protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 如果读锁非0且非同一个线程,失败
             * 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)
                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;
        }

可以看出与 tryWriteLock的唯一区别就是 writerShouldBlock 检测;这也是公平与非公平子类唯一的工作;

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

写锁,是独占锁,独占锁的释放我们已经很熟悉了。

读锁与 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.
             * 	当前线程有资格获取读锁(队列中的首元素不是写等待线程)
             *  尝试cas锁定并更新读锁数量,这一步尚未检测重入场景;比如虽然不允许我们获取读锁,但是我们曾经是第一个获取过读锁的线程,或者是我们曾经获取过读锁,且仍然保持着读锁,即时此时队列中已经有排队的节点无论是否写线程或者读线程
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             *  如果应该阻塞或者cas失败或者读数量饱和则进行循环版本完整的 fullTryAcquireShared
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

        /**
         * Full version of acquire for reads, that handles CAS misses
         * 完整版本获取读锁,这里处理cas失败和重入读
         * and reentrant reads not dealt with in 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) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // 如果我们自己持有排他锁,阻塞在这里会导致死锁;即锁降级需要
                    // would cause deadlock.
                    // 写锁为 0 但 需要阻塞的场景
                } 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)
                                    // 当前线程计数器如果为0则清除
                                    readHolds.remove();
                            }
                        }
                        // 判断当前线是否持有过读锁
                        if (rh.count == 0)
                            return -1;
                    }
                }
                // 最大读锁量检查
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 此时,已经允许我们获取锁,cas操作,失败则继续循环
                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;
                }
            }
        }

tryAcquireShared 与 tryReadLock逻辑一致,只是多了 shouldBlock检查,以及对重入的允许;在这里我们可以看到,可以在持有写锁的同时,本线程在获取读锁。

在写锁中获取读锁

在Doug Lee 给的例子中,获取写锁完毕,再释放写锁之前,先获取读锁;
这样做的原因是防止脏读,因为我们的data是普通对象,所以如果我们先释放写锁,别的线程恰巧获取到写锁,然后对数据进行了更该,而对于我们自己的线程是看不到被更改的内容;

class CachedData {
 *   Object data;
 *   volatile boolean cacheValid;
 *   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 *
 *   void processCachedData() {
 *     rwl.readLock().lock();
 *     if (!cacheValid) {
 *       // Must release read lock before acquiring write lock
 *       rwl.readLock().unlock();
 *       rwl.writeLock().lock();
 *       try {
 *         // Recheck state because another thread might have
 *         // acquired write lock and changed state before we did.
 *         if (!cacheValid) {
 *           data = ...
 *           cacheValid = true;
 *         }
 *         // Downgrade by acquiring read lock before releasing write lock
 *         rwl.readLock().lock();
 *       } finally {
 *         rwl.writeLock().unlock(); // Unlock write, still hold read
 *       }
 *     }
 *
 *     try {
 *       use(data);
 *     } finally {
 *       rwl.readLock().unlock();
 *     }
 *   }
 * }}
读锁 与 tryReleaseShared

tryReleaseShared很简单,先减去当前线程持有的锁数量,再减去总体资源state中持有的数量;

		protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                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;
            }
            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;
            }
        }
写锁饥饿与悲观读

我们在非公平锁处看到过对写锁的特权,直接放行;也看到了对读锁的限制,当队首元素是写等待线程时,应该阻塞;Doug Lee 说这只是一种概率事件,还是可能会导致写锁饥饿;
所以,如果想避免写锁饥饿的话,可以使用公平锁来解决,新来的读线程,也应该入队,只要队列中其他等待线程。
可重入读写锁的读锁期间不允许写锁,所以这也是一种悲观读锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值