跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • AQS 里的 setHeadAndPropagate 以及关于 PROPAGATE 信号的疑问?
未分類
21 5 月 2020

AQS 里的 setHeadAndPropagate 以及关于 PROPAGATE 信号的疑问?

AQS 里的 setHeadAndPropagate 以及关于 PROPAGATE 信号的疑问?

資深大佬 : amiwrong123 1

    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();         }     } 
  1. 上面的 h == null (h = head) == null s == null 看起来好像只是为了防止空指针异常,还是真的会出现这些情况,这些情况都是啥场景啊?
  2. h == null 和(h = head) == null 为啥要检查两遍,看起来是 下一个时间节点,head 就可能变了,但如果考虑这种情况,检查两遍 难道就够吗?
    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. 上面这个函数看起来 一般情况下,只会执行一次循环(头节点没有变)
  2. 为啥要从 SIGNAL 到 0 再到 PROPAGATE 呢,是为了防 unparkSuccessor 一手吗?
  3. 感觉好多疑问,都不知如何说起…
大佬有話說 (4)

  • 主 資深大佬 : amiwrong123

    我好后悔,我是不是应该弄个吸引人的标题的…

  • 資深大佬 : guyeu

    1. 输入不确定的情况下,就是得检查;
    2. 这纯粹是语言基础问题。。检查两遍是因为检查的是俩不同的对象;

    1. 运行期间头结点是有可能变的;
    2. 啥叫从 SIGNAL 到 0 再到 PROPAGATE,那特么是俩逻辑分支;
    3. 别说了;

  • 資深大佬 : luckyrayyy

    https://mingshan.fun/2019/02/02/aqs-shared/
    你看这篇文章,检查两边是因为这是两个对象,一个是之前的头结点,一个是新的头结点。

  • 主 資深大佬 : amiwrong123

    @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`就有点想不通,感觉他只会造成不必要的唤醒?好奇怪

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具