Semaphore笔记

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笔记)