图解AQS的设计与实现,手摸手带你实现一把互斥锁! (3)

图解AQS的设计与实现,手摸手带你实现一把互斥锁!

头节点后续节点获取同步状态失败。

图解AQS的设计与实现,手摸手带你实现一把互斥锁!

三,共享模式获取与释放状态

共享模式和独占模式最主要的区别是在支持同一时刻有多个线程同时获取同步状态。为了避免带来额外的负担,在上文中提到的同步队列中都是用独占模式进行讲述,其实同步队列中的节点应该是独占和共享节点并存的。

图解AQS的设计与实现,手摸手带你实现一把互斥锁!

接下来将针对共享模式状态下获取与释放状态的过程,图文并茂得进行分析。

3.1 获取同步状态

首先至少要调用一次tryAcquireShared(arg)方法,如果返回值大于等于0表示获取成功。

当获取锁失败时,则创建一个共享类型的节点并进入一个同步队列,然后进入队列中进入自旋状态(阻塞,唤醒两种状态来回切换,直到获取到同步状态为止)

当队列中的等待线程被唤醒以后就重新尝试获取锁资源,如果成功则唤醒后面还在等待的共享节点并把该唤醒事件传递下去,即会依次唤醒在该节点后面的所有共享节点,否则继续挂起等待。

图解AQS的设计与实现,手摸手带你实现一把互斥锁!

当一个同享节点获取到同步状态,并唤醒后面等待的共享状态的结果如下图所示:

图解AQS的设计与实现,手摸手带你实现一把互斥锁!

/** * 共享模式获取同步状态; * 1. 首先至少要调用一次tryAcquireShared(arg)方法,如果返回值大于等于0表示获取成功,直接返回结果即可 * 2. 否则,将会加入到同步队列中,反复阻塞与唤醒,直到获取同步状态成功为止; 获取成功后会唤醒后面还在等待的共享节点并把该唤醒事件传递下去,即会依次唤醒在该节点后面的所有共享节点 */ public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } /** * 2. 自旋模式获取同步状态 */ private void doAcquireShared(int arg) { // 2.1 第一次获取失败后,会将此线程加入到同步队列中 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { // 如果前驱节点是头节点,尝试获取同步状态 final Node p = node.predecessor(); if (p == head) { // r > 0表示获取同步状态成功,并且还有共享类型节点在同步队列中 // r == 0 表示获取同步状态成功,同步队列中没有其他共享模式节点 int r = tryAcquireShared(arg); if (r >= 0) { // !!!! 获取同步状态成功后,将当前node设置为头节点,并向后传播,唤醒共享模式等待的节点 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } /** * 设置新的头结点,并设置后面需要唤醒的节点 */ private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node); // propagate > 0 表明后面需要唤醒的共享模式节点 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; // 如果当前节点的后继节点是共享类型或者没有后继节点,则进行唤醒 // 这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒 if (s == null || s.isShared()) doReleaseShared(); } } /** * 唤醒所有共享模式节点 */ private void doReleaseShared() { for (;;) { // 唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了 // 其实就是唤醒上面新获取到共享锁的节点的后继节点 Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; // 表示后继节点需要被唤醒 if (ws == Node.SIGNAL) { //这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; //执行唤醒操作 unparkSuccessor(h); } //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; } // 如果头结点没有发生变化,表示设置完成,退出循环 // 如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试 if (h == head) break; } }

最后,获取到同步状态的线程执行完毕,同步队列中只有一个独占节点:

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

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