美文网首页
Semaphore笔记

Semaphore笔记

作者: ppyy_power | 来源:发表于2019-06-18 15:59 被阅读0次

    doReleaseShared ,一个比较特殊的方法,由于共享的特性,在获取锁和释放锁的过程都需要唤醒后继节点,因为可以有多个线程同时进入临界区。这个方法的主要作用是:
    1.在执行 acquireShared 申请资源时,执行 tryAcquireShared 失败则执行 doAcquireShared 方法将线程加入同步队列,然后判断同步队列中是否只有当前这一个等待线程,是则自旋获取锁,如果获取到了锁,就把当前节点设为头结点。然后 setHeadAndPropagate 继续唤醒后继节点,因为上次释放的资源可能特别多,现在的资源支持较多的线程同时执行,所以要唤醒后继节点。唤醒后继节点的条件是 资源数 > 0 或者 当前节点的后继节点的 waitStatus < 0 在这里只能是 PROPAGATE 或者 SIGNAL。这里也就解释了为什么在 doReleaseShared 中需要设置头为 PROPAGATE 状态,就是为了后续的唤醒。
    2.而在执行 releaseShared 释放资源时,首先执行 tryReleaseShared ,如果成功则执行 doReleaseShared 释放后继节点,因为释放了资源让更多的线程来运行。如果说现在队列中没有任何节点在等待就把头结点设置为 PROPAGATE ,说明有过剩的资源可用。如果此时刚好遇到上面执行 acquireShared 需要唤醒后继节点的时候先要判断头结点的 waitStatus 的值,如果是 PROPAGATE 肯定可以唤醒后面的。如果说等待队列中有等待线程那么就唤醒他们就行。
    3.好了这个 doReleaseShared 方法需要在以上两种情况下分析,否则始终不清楚为什么要设置 PROPAGATE 。

    // 这个方法中与 release 中的唤醒不同点在于他保证了释放动作的传递
        // 如果后继节点需要唤醒,则执行唤醒操作,如果没有后继节点则把头设置为 PROPAGATE
        // 这里的死循环和其他的操作中的死循环一样,为了检测新的节点进入队列
        // 其实这个方法比较特殊,在 acquireShared 和 releaseShared 中都被执行了,主要就是共享模式允许多个线程进入临界区
        private void doReleaseShared() {
            for (;;) {
                Node h = head;
                // 等待队列中有正在等待的线程
                if (h != null && h != tail) {
                    // 获取头节点对应的线程的状态
                    int ws = h.waitStatus;
                    // 如果头节点对应的线程是SIGNAL状态,则意味着头结点正在运行,后继结点所对应的线程需要被唤醒。
                    if (ws == Node.SIGNAL) {
                        // 修改头结点,现在后继节点要成为头结点了,状态设置初始值
                        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                            continue;
                        // 唤醒头结点h的后继结点所对应的线程
                        unparkSuccessor(h);
                    }
                    // 队列中没有等待线程,只有一个正在运行的线程。
                    // 将头结点设置为 PROPAGATE 标志进行传递唤醒
                    else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                        continue;                // loop on failed CAS
                }
                // 如果头结点发生变化有一种可能就是在 acquireShared 的时候会调用 setHeadAndPropagate 导致头结点变化,则继续循环。
                // 从新的头结点开始唤醒后继节点。
                if (h == head)                   // loop if head changed
                    break;
            }
        }
    
        //  被 acquireShard 调用
        private void setHeadAndPropagate(Node node, int propagate) {
            Node h = head; // Record old head for check below
            // 当前线程设为头结点
            setHead(node);
            // propagate 代表的是当前剩余的资源数,如果还有资源就唤醒后面的共享线程,允许多个线程获取锁,
            // 或者还有一个比较有意思的条件就是 h.waitStatus < 0 他其实是说 h.waitStatus 要么是 signal 要么是 propagate
            // 从而唤醒后继节点
            if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
                Node s = node.next;
                if (s == null || s.isShared())
                    doReleaseShared();
            }
        }
    

    相关文章

      网友评论

          本文标题:Semaphore笔记

          本文链接:https://www.haomeiwen.com/subject/skwfqctx.html