发布于2026-07-11 阅读(0)
扫一扫,手机访问
先理解一个核心事实:ConcurrentLinkedQueue 内部那个叫 HOPS 的机制,并不是什么公开的 API 或可配置参数,而是 Doug Lea 在源码里埋的一个「跳步计数」策略。简单说,当 tail 节点明显滞后(比如它的 next 已经指向了别的节点),线程会直接跳过这个过期的 tail,从更靠后的节点开始找真正的尾节点。这样一来,大量针对旧 tail 的无效 CAS 操作就被绕开了,尤其在高竞争环境下——多个线程同时试图更新同一个过期 tail 时,90% 的 CAS 都会失败并自旋,HOPS 恰好就是避开这种内部消耗的巧妙设计。

HOPS 不减少单次入队操作的 CAS 次数,但它大幅降低了「无效 CAS 尝试」的比例。你想想看,当 tail 已经过时,线程还拿它去做 casNext,大概率会失败,然后重试,白白浪费 CPU。HOPS 的逻辑就是:如果当前 tail 的 next 非空,就别在它身上浪费时间了,直接从 tail.next 往后找,一步到位找到 next == null 的节点。这个跳跃过程可能跨过多个中间节点,所以才叫“hop”。
ConcurrentLinkedQueue 的 tail 从来不是实时指向物理尾节点,它更像一个“乐观缓存”——允许滞后。只要 tail 的 next 字段不是 null,就说明它已经不是真正的尾部了;这时候若继续用这个 tail 做 casNext,大概率失手。HOPS 的触发条件很明确:当前 tail 的 next != null,并且这个 next 不是哨兵节点(也就是排除 p == q 那种自循环情况)。实际的跳转逻辑藏在 offer() 循环里:else p = (p != t && t != (t = tail)) ? t : q; —— 这里的 q 就是 p.next,本质就是 hop 一步。注意,HOPS 没有固定步长,它一次只跳一格 next,但因为循环中多次执行,效果是快速甩开那个陈旧的 tail。
你没办法通过任何方法开启或关闭 HOPS,它完全由内部状态驱动。不过,有些操作会隐形地干扰 HOPS 的效果:
size():它会强制遍历整个链表,间接导致 head/tail 被重置或校准,削弱 tail 缓存的价值。poll() 和 offer() 且出队极快:head 快速前移,可能让 tail 更新逻辑更保守,HOPS 触发频率随之下降。tail 字段):直接破坏内部一致性,HOPS 判断失效,CAS 失败率陡增。HOPS 的收益集中在「多生产者 + 中低出队压力」的场景。当线程数 ≥ 8 且平均入队间隔小于出队间隔时,HOPS 能带来 10%–20% 的吞吐提升;但如果出队和入队几乎同时发生(比如每个 offer() 后立即跟一个 poll()),tail 更新会被出队逻辑牵连,HOPS 触发变少,收益缩至 5% 以内。更重要的是,HOPS 本身不保证 tail 最终一致——tail 字段可能长期指向非尾节点,这正是它换来的代价:用最终一致性,换掉大量无意义的 CAS 尝试。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8