AQS 里 hasQueuedPredecessors 里为啥要先读取 tail 成员啊?

2020-05-17 13:38:46 +08:00
 amiwrong123
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

如上,注释说了因为第一次入队,可能出现 head 初始化了,head.next 没有初始化。那就是定位到如下地方:

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))//先设置 head
                    tail = head;//再设置 tail
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

看了网上的解释,都说这样就可以让 hasQueuedPredecessors 中两个域读取完只有几种情况发生:

  1. tail 为 null,head 不为 null (相对的,不会出现相反的情况)
  2. 都为 null
  3. 都不为 null

如下图,把 hasQueuedPredecessors 的两个读取域标记为蓝色,enq 的两次设置不标记颜色。无论怎么移动(保持先后相对位置),都只会出现上面几种情况。这好像证明了我的上面的观点。

但是此时,我又想起来多线程不是有个 happen-before 还是什么玩意,意思就是说,多线程下,每个线程只会考虑单线程下的正确执行。 而且 hasQueuedPredecessors 里 读取 tail 和读取 head 没有什么依赖关系,那上图的 标记蓝色的 读取 tail 和读取 head 岂不是可以随便交换顺序,也就不能维持 ,读取 tail 先与 读取 head 的相对顺序了吗?那岂不是 就会出现 tail 为 null,head 不为 null 的相反情况了?

哎,想太多,要疯了。

2241 次点击
所在节点    Java
5 条回复
xingda920813
2020-05-17 14:26:35 +08:00
amiwrong123
2020-05-17 15:25:28 +08:00
@xingda920813
这篇我看了,大概就是我图片里那个意思。 但需要考虑什么 happen-before 语义不呢
liangdu
2020-05-17 19:54:51 +08:00
颠倒两句赋值语句是否会影响最终的结果关键在于 return 的写法是否有考虑 4 种情况(如果是单线程,只有 2 种,要么全为空,要么,全为非空),

明显上面代码只 return 逻辑只考虑 3 种情况(没考虑 tail 非空,head 空的情况会空指针异常)


emmmmm 你要说这代码不好?但是不这么写你的逻辑就要加个多一次判断了,重新给 head 赋值了。

至于你说的 happenbefore 原则,推理得不错,没想多,自信点,只是你对“语序逻辑是否依赖”没理解对而已,其实是有依赖的,所以 CAS 最恶心的地方就是为了降低锁的粒度而不得面对更复杂的场景(结果是好的,但增加理解的难度,优劣就不讨论了)。
amiwrong123
2020-05-17 20:18:30 +08:00
@liangdu
>只是你对“语序逻辑是否依赖”没理解对而已,其实是有依赖的

我怎么看怎么赶脚 Node t = tail; Node h = head;这两句没啥依赖关系啊。

对了,我又突然想起来了,难道是这是两次 volatile 写操作,然后会在每次 volatile 写后面,加 StoreStore 和 StoreLoad 屏障,然后才能 这两句 不会被 指令重排序 ?是因为这个原因吗
liangdu
2020-05-18 00:03:50 +08:00
@amiwrong123 有 cas 地方都有 volatile 的,不能单单讨论两句赋值依赖性,要结合整个函数来来看是否线程安全。

这里不存在重排问题,只是涉及 cas 可见性而已。但和我们上面讨论的不是一起回事哦!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/672555

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX