concurrentlinkedqueue中p.next == p是标记节点,表示该节点已被逻辑删除;其通过cas将next设为自身实现惰性删除,遍历中遇此情况即跳过并重试,由后续操作“顺手帮助”绕过,最终由gc回收,属正常无锁设计。

ConcurrentLinkedQueue 中的 p.next == p 是一种**标记节点(marker node)**,用于表示该节点已被逻辑删除,是无锁并发算法中实现“惰性删除”的关键机制,不是 bug 或异常状态。
为什么会出现 p.next == p?
当一个节点被出队(poll)或跳过(如在 findAndDelete 过程中),队列不会立即物理移除它,而是通过 CAS 将其 next 字段设为自身引用(p.next = p)。这样后续遍历线程一旦遇到 node.next == node,就能立刻识别该节点已失效,从而跳过它,避免因内存重排序或延迟可见性导致的 ABA 问题或访问已释放节点。
遍历逻辑如何安全跳过自引用节点?
所有内部遍历(如 peek、poll、iterator)都遵循统一模式:
- 读取当前节点
next - 若
next == null→ 到达队尾,停止 - 若
next == next.next(即next是自引用节点)→ 跳过该next,继续从当前节点重试next的读取(help delete 或 advance) - 否则 → 正常前进到
next
例如在 updateHead 辅助清理时,会用类似 casNext(p, p, next) 帮助将前驱节点绕过这个自引用节点,推动物理删除。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
自引用节点会被真正清除吗?
不会主动“回收”内存,但会被自然淘汰:
- 一旦前驱节点通过 CAS 成功将
next指向更后面的非空/非自引用节点,该自引用节点就彻底不可达 - 之后由 GC 自动回收(无强引用链指向它)
- 队列不维护专门的清理线程,依赖正常操作(入队/出队)过程中的“顺手帮助(helping)”完成清理
这种设计兼顾了无锁性能与内存安全性,避免了全局锁或周期性扫描开销。
开发者需要注意什么?
你不需要、也不应该手动判断或修复 p.next == p:
- 这是队列内部实现细节,对外透明;用户调用
poll()、peek()等方法时,结果已自动排除自引用节点 - 不要在自定义遍历中忽略该检查,否则可能陷入无限循环(比如直接 while(node != null) { node = node.next; } 而不校验自引用)
- 调试时看到
next == this是正常现象,不代表数据结构损坏
本质上,p.next == p 是一种轻量、原子、无锁的“墓碑标记”,是 Doug Lea 在 ConcurrentLinkedQueue 中对 Michael-Scott 队列算法的重要工程化改进。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










