如何通过 ConcurrentLinkedQueue 的 HOPS 机制大幅降低 CAS 操作频率以优化高并发入队效率?
- 内容介绍
- 文章标签
- 相关推荐
本文共计672个文字,预计阅读时间需要3分钟。
HOPS并非公开API或配置项,而是Doug Lea在ConcurrentLinkedQueue内部使用的一种跳跃计数策略:
tail 节点为什么容易过期?怎么触发 HOPS 跳转
ConcurrentLinkedQueue 的 tail 并非实时指向物理尾节点,而是一个“乐观缓存”。只要 tail 的 next 字段非 null,就说明它已不是真正尾部;此时若继续用它做 casNext,大概率失败。HOPS 就是在这种判断成立时,让线程放弃 tail,转而从 tail.next 开始向后遍历,直到找到 next == null 的节点——这个过程可能跳过若干中间节点,所以叫 “hop”。
- 触发条件:当前 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,它完全由内部状态驱动。但有些操作会隐式干扰 HOPS 效果:
- 频繁调用
size():它会强制遍历整个链表,间接导致 head/tail 被重置或校准,削弱 tail 缓存价值 - 混用
poll()和offer()且出队极快:head 快速前移,可能让 tail 更新逻辑更保守,HOPS 触发频率下降 - 手动修改队列结构(如反射篡改
tail字段):直接破坏内部一致性,HOPS 判断失效,CAS 失败率陡增 - 在 offer 循环外持有 old tail 引用并反复重试:绕过 HOPS 自动跳转逻辑,等于主动退化成朴素 CAS 自旋
实测中 HOPS 对性能的影响边界在哪
HOPS 的收益集中在「多生产者 + 中低出队压力」场景。当线程数 ≥ 8 且平均入队间隔 poll()),tail 更新会被出队逻辑牵连,HOPS 触发变少,收益缩至 5% 以内。更重要的是:HOPS 本身不保证 tail 最终一致,tail 字段仍可能长期指向非尾节点——这正是它换来的代价:用最终一致性,换掉大量无意义 CAS。
本文共计672个文字,预计阅读时间需要3分钟。
HOPS并非公开API或配置项,而是Doug Lea在ConcurrentLinkedQueue内部使用的一种跳跃计数策略:
tail 节点为什么容易过期?怎么触发 HOPS 跳转
ConcurrentLinkedQueue 的 tail 并非实时指向物理尾节点,而是一个“乐观缓存”。只要 tail 的 next 字段非 null,就说明它已不是真正尾部;此时若继续用它做 casNext,大概率失败。HOPS 就是在这种判断成立时,让线程放弃 tail,转而从 tail.next 开始向后遍历,直到找到 next == null 的节点——这个过程可能跳过若干中间节点,所以叫 “hop”。
- 触发条件:当前 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,它完全由内部状态驱动。但有些操作会隐式干扰 HOPS 效果:
- 频繁调用
size():它会强制遍历整个链表,间接导致 head/tail 被重置或校准,削弱 tail 缓存价值 - 混用
poll()和offer()且出队极快:head 快速前移,可能让 tail 更新逻辑更保守,HOPS 触发频率下降 - 手动修改队列结构(如反射篡改
tail字段):直接破坏内部一致性,HOPS 判断失效,CAS 失败率陡增 - 在 offer 循环外持有 old tail 引用并反复重试:绕过 HOPS 自动跳转逻辑,等于主动退化成朴素 CAS 自旋
实测中 HOPS 对性能的影响边界在哪
HOPS 的收益集中在「多生产者 + 中低出队压力」场景。当线程数 ≥ 8 且平均入队间隔 poll()),tail 更新会被出队逻辑牵连,HOPS 触发变少,收益缩至 5% 以内。更重要的是:HOPS 本身不保证 tail 最终一致,tail 字段仍可能长期指向非尾节点——这正是它换来的代价:用最终一致性,换掉大量无意义 CAS。

