【AQS】共享锁的释放

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

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

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

文章首发于:clawhub.club


共享锁的释放的主要方法doReleaseShared()已经在共享所的获取中简单的分析了一下,可见共享锁触发唤醒后记节点的行为有两处:第一,当前节点成功获取共享锁后,第二,当前节点释放共享锁后。
下面分析共享锁释放的主要逻辑代码:

     /**
         * Releases in shared mode.  Implemented by unblocking one or more
         * threads if {@link #tryReleaseShared} returns true.
         *
         * @param arg the release argument.  This value is conveyed to
         *            {@link #tryReleaseShared} but is otherwise uninterpreted
         *            and can represent anything you like.
         * @return the value returned from {@link #tryReleaseShared}
         */
        public final boolean releaseShared(int arg) {
            //子类实现释放锁
            if (tryReleaseShared(arg)) {
                //释放锁
                doReleaseShared();
                return true;
            }
            return false;
        }

其中的tryReleaseShared(arg)由子类实现,Semaphore的内部类Sync中的实现为:

    protected final boolean tryReleaseShared(int releases) {
                for (;;) {
                    int current = getState();
                    int next = current + releases;
                    if (next < current) // overflow
                        throw new Error("Maximum permit count exceeded");
                    if (compareAndSetState(current, next))
                        return true;
                }
            }

看源码比较简单,for循环+CAS+状态修改。
方法:doReleaseShared()比较复杂:

      /**
         * Release action for shared mode -- signals successor and ensures
         * propagation. (Note: For exclusive mode, release just amounts
         * to calling unparkSuccessor of head if it needs signal.)
         * 共享模式释放锁
         */
        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;
                    //如果头节点的状态时SIGNAL,则需要唤醒其后继节点
                    if (ws == Node.SIGNAL) {
                        //设置节点状态为0,失败则从新开始循环
                        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                            continue;            // loop to recheck cases
                        //设置成功后,唤醒后继节点
                        unparkSuccessor(h);
                    } else if (ws == 0 &&
                            !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                        //将头节点状态设置为PROPAGATE
                        continue;                // loop on failed CAS
                }
                //如果头节点改变了,继续循环
                if (h == head)                   // loop if head changed
                    break;
            }
        }
      /**
         * Wakes up node's successor, if one exists.
         * 唤醒node的后继节点(如果存在的话)。
         * 释放锁和取消获取锁时被调用
         *
         * @param node the node
         */
        private void unparkSuccessor(Node node) {
            /*
             * If status is negative (i.e., possibly needing signal) try
             * to clear in anticipation of signalling.  It is OK if this
             * fails or if status is changed by waiting thread.
             * 如果状态为负,清除预期信号,如果此操作失败或状态被等待线程更改,则没有问题。
             * CANCELLED = 1
             * 0
             * SIGNAL = -1
             * CONDITION = -2
             * PROPAGATE = -3
             */
            int ws = node.waitStatus;
            if (ws < 0)
                compareAndSetWaitStatus(node, ws, 0);

            /*
             * Thread to unpark is held in successor, which is normally
             * just the next node.  But if cancelled or apparently null,
             * traverse backwards from tail to find the actual
             * non-cancelled successor.
             * Thread to unpark被保存在后续节点中,它通常只是下一个节点,
             * 但是,如果取消或明显为空,则从tail向后遍历以找到实际的非取消后继。
             */
            Node s = node.next;
            if (s == null || s.waitStatus > 0) {
                s = null;
                //从后向前遍历节点,最后s为离当前节点最近的需要被唤醒的节点
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            //唤醒操作
            if (s != null)
                LockSupport.unpark(s.thread);
        }

这里面比较复杂点在于waitStatus状态的判断

  • ws值为Node.SIGNAL,则说明后继节点需要唤醒
  • ws为0是指当前队列的最后一个节点成为了头节点

唤醒头节点的后一个节点的效率很高,因为会有多个线程同时执行唤醒头节点的下一个节点。


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

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【AQS】共享锁的释放

评论 抢沙发

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

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

支付宝扫一扫打赏

微信扫一扫打赏