这段代码的执行逻辑与非共享模式的执行逻辑相似,这里不赘述。我们重点关注的是“当再次尝试,获取成功之后的setHeadAndPropagate方法的处理”。这个方法的处理是与非共享模式不一样的。setHeadAndPropagate的代码:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//替换head节点。因为head表示当前正在执行的节点。
//节点已经获取成功,则将会被执行,所以需要替换head
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared()) //获取当前节点的下一个节点,判断是否是共享模式的节点
//如果是共享模式的节点,则唤醒nextNode。doReleaseShared方法其实就是semaphore.release()
//的内部实现。具体的实现,下文release方法中进行说明
doReleaseShared();
}
}可以看到,在非共享模式中,如果获取成功后,仅仅只是直接替换了head节点,并没有唤醒nextNode节点的操作;而对于共享模式,则进行了唤醒操作。所以这个唤醒的操作将是共享模式的核心。
二、共享的release方法
代码:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { //尝试释放,如果释放成功,则执行doReleaseShared
doReleaseShared();
return true;
}
return false;
}
类Semaphore.Sync中的tryReleaseShared(arg)方法的实现如下:
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases; //acquire是减少,release就是增加
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
接下来重点看一下doReleaseShared()方法:
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus; //获取head节点的状态
//判断head节点的状态,如果head节点的状态是SIGNAL,
//表示head节点存在nextNode,需要唤醒nextNode
if (ws == Node.SIGNAL) {
//先尝试修改head节点的状态,如果成功,则唤醒nextNode,如果失败,则继续尝试
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
//如果head节点的状态是0,则尝试修改为PROPAGATE;
//PROPAGATE表示下一个节点可以无条件的进行acquire,也就是下一次acquire一定会成功,即信号量充足
//如果head节点的状态是PROPAGATE,则不做任何处理,直接退出循环返回。
}
if (h == head) //跳出循环
break;
}
}
以上基本就是AQS的共享模式的实现。对于doReleaseShared()方法中对于head节点的SIGNAL状态判断,很可能会有疑惑。疑惑在于:head节点什么时候才会为SIGNAL状态呢?
?? ?? ?? 对于这个疑惑,首先先要知道SIGNAL状态表示什么。SIGNAL状态表示节点有nextNode,即有后续处理节点。在当前的节点处理完之后,需要唤醒nextNode。以Semaphore类举例:
?? ?? ??当我们的5个信号量都用完的时候,有新的线程进入(下文称作newThread)。此时,newThread就会被挂起。这个挂起的过程需要经过两个个步骤:
1、在node队列增加一个node,且这个node是共享模式的
2、判断新增加的node的prevNode(前节点)是否为SIGNAL状态。如果是SIGNAL状态,则直接挂起线程;如果不是SGINAL状态,则先将前一个node的状态修改SIGNAL状态,再挂起线程。
关于上面两个步骤的实现在shouldParkAfterFailedAcquire方法和doAcquireShared方法中
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-75627-2.html
在这个颜即正义的大网络时代