private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
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() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
上面引入的 PROPAGATE 状态,就是为了 setHeadAndPropagate 能够检测到而存在的吧。
1
amiwrong123 OP 我好后悔,我是不是应该弄个吸引人的标题的…
|
2
guyeu 2020-05-22 15:36:18 +08:00
1. 输入不确定的情况下,就是得检查;
2. 这纯粹是语言基础问题。。检查两遍是因为检查的是俩不同的对象; 1. 运行期间头结点是有可能变的; 2. 啥叫从 SIGNAL 到 0 再到 PROPAGATE,那特么是俩逻辑分支; 3. 别说了; |
3
luckyrayyy 2020-05-22 15:44:11 +08:00
https://mingshan.fun/2019/02/02/aqs-shared/
你看这篇文章,检查两边是因为这是两个对象,一个是之前的头结点,一个是新的头结点。 |
4
amiwrong123 OP @guyeu
@luckyrayyy 谢谢回复,之前这块确实想错了,看来以后发帖前还是得看仔细,我承认错误。 不过看完还是有疑问。分析如下 - 入参`node`所代表的线程(这个 node 的 Thread 成员)一定是当前执行的线程, - 看第一个 if 的判断: - 如果`propagate > 0`成立的话,说明还有剩余共享锁可以获取,那么短路后面条件。 - 如果`propagate = 0`成立的话,说明没有剩余共享锁可以获取了,按理说不需要唤醒后继的。也就是说,很多情况下,调用 doReleaseShared,会造成 acquire thread 不必要的唤醒。 - 继续看,如果`propagate > 0`不成立,而`h.waitStatus < 0`成立。这说明旧 head 的 status<0 。但如果你看 doReleaseShared 的逻辑,会发现在 unparkSuccessor 之前就会 CAS 设置 head 的 status 为 0 的,在 unparkSuccessor 也会进行一次 CAS 尝试,因为 head 的 status 为 0 代表一种中间状态( head 的后继代表的线程已经唤醒,但它还没有做完工作),或者代表 head 是 tail 。而这里旧 head 的 status<0,只能是由于 doReleaseShared 里的`compareAndSetWaitStatus(h, 0, Node.PROPAGATE)`的操作,而且由于当前执行 setHeadAndPropagate 的线程只会在最后一句才执行 doReleaseShared,所以出现这种情况,一定是因为有另一个线程在调用 doReleaseShared 才能造成,而这很可能是因为在中间状态时,又有人释放了共享锁。 - 继续看,如果`propagate > 0`不成立,且`h.waitStatus < 0`不成立,而第二个`h.waitStatus < 0`成立。注意,第二个`h.waitStatus < 0`里的 h 是新 head (很可能就是入参 node )。第一个`h.waitStatus < 0`不成立很正常,因为它一般为 0 。第二个`h.waitStatus < 0`成立也很正常,因为只要新 head 不是队尾,那么新 head 的 status 肯定是 SIGNAL 。所以这种情况只会造成不必要的唤醒。 简单的说,我认为,检查第一个`h.waitStatus < 0`,是因为被唤醒的线程处于中间状态,而 doReleaseShared 在这个中间状态,只会设置`compareAndSetWaitStatus(h, 0, Node.PROPAGATE)`,不会调用 unparkSuccessor(h);,因为线程已经被唤醒了。 但检查第二个`h.waitStatus < 0`就有点想不通,感觉他只会造成不必要的唤醒?好奇怪 |