Java并发编程系列-AbstractQueuedSynchronizer (9)

tryAcquireShared方法源码:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } }

该方法是一个模板方法,需要子类来完善逻辑。但大致意义如下,如果获取失败返回负数(-1),如果是该同步状态被首次共享获取成功,返回0,非首次获取成功,则返回正数(1)

doAcquireShared方法源码:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { private void doAcquireShared(int arg) { // 将线程封装成功节点,保存到同步队列 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) {// 自旋 final Node p = node.predecessor();// 获取前置节点 if (p == head) { // 如果前置节点为头节点 int r = tryAcquireShared(arg); if (r >= 0) { // 如果成功获取到同步状态,则将当前节点置为头节点,并进行传播唤醒 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } // 如果前置节点非头节点或者获取同步状态失败,则将前置节点设置为SIGNAL,然后阻塞当前线程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // 预存原始头节点 setHead(node);// 将当前节点置为头节点 // propagate可为0或1,0表示同步状态被首次获取,1表示被多次获取 // h为原始头节点 // head为新头节点 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next;// 获取下级节点s // 如果后继节点不存在或者后继节点是共享式的,则唤醒后继节点 if (s == null || s.isShared()) doReleaseShared();// 唤醒后继节点 } } }

解析:该方法的逻辑相对于acquireQueued只是稍有变动,大致意思是相同的。不同之处在于此处涉及到一个传播(Propagate)。
所谓的传播,其实是在当前节点共享式获取到同步状态之后,检查其后置节点是否也是在等待共享式获取同步状态,若是,则将唤醒其后置节点。

doReleaseShared源码:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { private void doReleaseShared() { for (;;) {// 自旋 Node h = head;// 获取头节点 if (h != null && h != tail) {// 如果队列中存在多个节点的话 int ws = h.waitStatus;// 头节点状态ws // 如果头节点状态为SIGNAL,则将其 if (ws == Node.SIGNAL) {// 说明其后继节点线程被阻塞,需要唤醒 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))// 首先将头节点状态重置为0 continue;// 如果重置头节点状态操作失败则重试 unparkSuccessor(h);// 然后进行后继节点唤醒 } // 如果头节点状态为0,则将其状态更新为PROPAGATE else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue;// 头节点更新操作失败则重试 } if (h == head) break;// 头节点发生变化则退出自旋 } } private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next;// 获取后继节点s if (s == null || s.waitStatus > 0) { // 如果s为null或者其状态为取消,则从后遍历队列节点,找到node节点之后的首个未被取消的节点t,赋给s s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread);// 执行s节点线程的唤醒操作 } }

解析:doReleaseShared方法被两处调用,一为此处,另一为releaseShared方法,这个是用来共享式释放同步状态的方法。
doReleaseShared方法的作用就是为了唤醒后继节点,主要逻辑如下:首先获取头节点的状态ws,如果ws是SIGNAL,
表示后继节点需要被唤醒,然后自旋将头节点状态更新为0,并执行后继节点唤醒操作,这里要确保唤醒的是头节点之后首个
未被取消的线程节点,唤醒之后,后继节点的线程开始继续执行,当前线程也继续执行;如果ws是0,则将头节点的状态更新为PROPAGATE,
来确保同步状态可以顺利传播(因为如果ws为SIGNAL,会自动唤醒下一个节点,而0则不会,所有将其更新为PROPAGATE,表示共享式获取的传播)
被唤醒的线程会重置头节点,一旦重置,当前线程在最后校验头节点那一步就会成功,然后执行break退出自旋。

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

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