ConcurrentLinkedQueue 的 offer 方法通过无限自旋 CAS 更新尾节点实现无锁插入,失败后检查并协助推进 tail 指针,不退避、不阻塞,仅在成功或 OOM 时终止。

ConcurrentLinkedQueue 的 offer 方法在 CAS 失败时会持续自旋重试,直到成功或发现队列已关闭(但该队列不支持关闭,所以实际是无限重试),核心逻辑围绕“尾节点的原子更新”展开。
尾节点的 CAS 更新是重试的主战场
每次 offer 都尝试用 CAS 将新节点设置为当前尾节点的 next 字段。若失败,说明有其他线程抢先修改了该 tail 的 next,此时需重新读取 tail(可能已变更),再尝试推进;若 tail 本身已滞后,则先协助更新 tail 指针再重试。
- 先通过
tail.get()获取当前尾节点 - 尝试 CAS 设置
tail.next = newNode - CAS 失败后,不立即退出,而是检查 tail 是否“过期”(即其 next 不为 null 或 tail 已不是逻辑尾)
- 若过期,调用
updateTail(tail, newNode)尝试推进 tail;否则继续下一轮 CAS
tail 滞后时的协助式推进
ConcurrentLinkedQueue 允许 tail 滞后于真实尾节点(即“松弛”设计),所以 CAS 失败后常伴随 tail 更新操作。这个过程本身也依赖 CAS:用新的候选节点(比如刚插入的 newNode 或其 next)尝试替换当前 tail。若再次失败,说明又有竞争,就继续循环。
-
updateTail内部仍使用 CAS 更新 tail 引用 - 它会从当前 tail 出发,沿 next 链向后查找最远可达的非 null 节点作为新 tail
- 整个过程无锁、无阻塞,纯靠 CAS + 自旋
没有退避或 yield,纯 CPU 自旋
与一些并发结构(如 LongAdder)不同,ConcurrentLinkedQueue 的 offer 不做任何线程让步(如 Thread.yield() 或 park),也不引入指数退避。CAS 失败后立刻重试,适合低冲突场景;高竞争下可能造成 CPU 空转,但这是为了最小化延迟和避免上下文切换开销。
立即学习“Java免费学习笔记(深入)”;
- 循环体中只有 volatile 读、CAS、简单判断,指令路径极短
- 实际性能取决于链表局部性及多核缓存一致性效率
- Java 9+ 中部分 CAS 实现底层可能触发轻量级暂停(如 pause 指令),但对应用层透明
终止条件只有成功或 OOM
offer 方法没有超时或重试次数限制。只要线程未被中断(它不响应中断)、内存充足,就会一直重试。唯一跳出循环的方式是某次 CAS 成功,或在创建新节点时触发 OutOfMemoryError(极少见)。
- 不会因“重试太多”而抛异常或返回 false
- 语义上保证:只要内存够、线程活着,最终一定插入成功
- 这也是它被称为“无界、线程安全、弱一致性”队列的原因之一


















