AQS之ReentrantReadWriteLock精讲分析上篇 (4)

由此我们知道,这里的调用循环事实上是一个优化操作,因为在我们执行到该方法的末尾的时候,unparkSuccessor基本上已经被调用过了,而由于现在是共享锁模式,所以被唤醒的后继节点极有可能已经获取到了共享锁,成为了新的head节点,当它成为新的head节点后,它可能还是要在setHeadAndPropagate方法中调用doReleaseShared唤醒它的后继节点。

明确了上面几个问题后,我们再来详细分析这个方法

private void doReleaseShared() { for (;;) { Node h = head; // 如果队列中存在后继线程也就是队列至少有两个节点 if (h != null && h != tail) { int ws = h.waitStatus; // 如果当前ws值为Node.SIGNAL,则说明后继节点需要唤醒,这里采用CAS操作先将 // Node.SIGNAL状态改为0,这是因为可能有大量的doReleaseShared方法在 // 同时执行,我们只需要其中一个执行unparkSuccessor(h)操作就行了,这里通过CAS // 操作保证了unparkSuccessor(h)只被执行一次。 if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } // 如果h节点的状态为0,需要设置为PROPAGATE用以保证唤醒的传播 // ws啥时候为0 // 一种是上面的compareAndSetWaitStatus(h, Node.SIGNAL, 0)会导致ws为0, // 但是很明显,如果是因为这个原因,则它是不会进入到else if语句块的。所以这里 // 的 ws为0是指当前队列的最后一个节点成为了头节点。为什么是最后一个节点呢,因为 // 每次新的节点加进来,在挂起前一定会将自己的前驱节点的waitStatus修 // 改成 Node.SIGNAL的 else if (ws == 0 && // compareAndSetWaitStatus(h, 0, Node.PROPAGATE)这个操作什么时候会失败? // 这个操作失败,说明就在执行这个操作的瞬间,ws此时已经不为0了,说明有新的节点 // 入队了,ws的值被改为了Node.SIGNAL,此时我们将调用continue,在下次循环中 // 直接将这个刚刚新入队但准备挂起的线程唤醒 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } // 检查h是否仍然是head,如果不是的话需要再进行循环 if (h == head) // loop if head changed break; } } private void unparkSuccessor(Node node) { // 获取当前节点的node.waitStatus 此时为 SIGNAL所以将当前节点的waitStatus // 设置成 0 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 获取后继节点 Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; // 往前寻找遍历找到第一个节点waitStatus 为SIGNAL的节点,为了唤醒其节点 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 如果不为空直接唤醒后继节点 if (s != null) LockSupport.unpark(s.thread); }

这里优化了一个点:

首先队列里至少有两个节点

其次要执行到else if语句,说明我们跳过了前面的if条件,说明头节点是刚刚成为头节点的,它的waitStatus值还为0,尾节点是在这之后刚刚加进来的,它需要执行shouldParkAfterFailedAcquire,将它的前驱节点(即头节点)的waitStatus值修改为Node.SIGNAL,但是目前这个修改操作还没有来的及执行。这种情况使我们得以进入else if的前半部分else if (ws == 0 &&

再次,要满足!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)这一条件,说明此时头节点的 waitStatus 已经不再是 0 了,这说明之前那个没有来得及执行的在shouldParkAfterFailedAcquire将前驱节点的的waitStatus值修改为Node.SIGNAL的操作现在执行完了。

注意:else if 的 && 连接了两个不一致的状态,分别对应了shouldParkAfterFailedAcquire的compareAndSetWaitStatus(pred, ws, Node.SIGNAL)执行成功前和执行成功后,因为doReleaseShared和shouldParkAfterFailedAcquire是可以并发执行的,所以这一条件是有可能满足的,可能只是一瞬间发生的。

3.获取读锁流程图

AQS之ReentrantReadWriteLock精讲分析上篇

流程解析:

读锁获取锁的过程比写锁稍微复杂些

首先判断写锁是否为0并且当前线程不占有独占锁,直接返回;

否则,判断读线程是否需要被阻塞并且读锁数量是否小于最大值并且比较设置状态成功,若当前没有读锁,则设置第一个读线程firstReader和firstReaderHoldCount;

若当前线程线程为第一个读线程,则增加firstReaderHoldCount;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppjjx.html