ConcurrentLinkedQueue 通过 CAS 指令和 AtomicReference 实现无锁,入队出队不加锁,仅对节点指针做原子操作;失败重试、高吞吐但 CPU 消耗大,适用于松耦合生产者-消费者场景。

ConcurrentLinkedQueue 是怎么做到无锁的
它靠的是 CAS(Compare-And-Swap)指令 + 原子引用更新,整个入队、出队过程不加 synchronized 也不用 ReentrantLock。核心是把链表节点的 next 字段声明为 AtomicReference,所有指针移动都通过 compareAndSet 尝试推进。
不是“完全没锁”,而是把锁的粒度降到单个节点指针级别;失败就重试,不阻塞线程。这带来高吞吐,但也意味着在极端竞争下可能反复 CAS 失败,消耗 CPU。
- 只适用于「生产者-消费者」松耦合场景,不适合需要强顺序或严格 FIFO 语义的业务逻辑
-
size()方法不可靠——它要遍历链表,过程中其他线程可能正在修改,返回值只是快照,不能用于条件判断 - 不支持 null 元素,插入
null会直接抛NullPointerException
offer() 和 poll() 的实际行为差异
offer() 总是返回 true(除非传入 null),它不等空间、不阻塞、不拒绝,只要内存够就能加到队尾;poll() 则是「有就拿,没有就立刻返回 null」,不会等待。
这两个方法都不抛异常(除了 null 入队),但语义上和 BlockingQueue 完全不同:它不提供阻塞、超时、满/空等待能力。
- 如果业务需要「队列满时背压」或「取不到时等一会儿」,别硬套
ConcurrentLinkedQueue,该换ArrayBlockingQueue或LinkedBlockingQueue -
poll()返回null只代表当前瞬间队列为空,不代表之后一直空——并发环境下,刚判空下一毫秒就被其他线程offer()了 - 不要用
poll()返回值做「是否还有任务」的长期状态判断,容易漏处理
为什么 peek() 不安全,以及怎么应对
peek() 只看队首不移除,看似轻量,但它仍要读取头节点的 item 字段——而这个字段可能已被其他线程 poll() 清空(设为 null),导致返回 null 即使队列非空。
这不是 bug,是设计使然:为了不引入额外同步开销,ConcurrentLinkedQueue 允许头节点的 item 在出队后暂不物理删除,只置空,靠后续清理逻辑异步回收。
- 不要把
peek() == null当作「队列空」的依据,它只说明「当前头节点 item 为空」 - 真正判断是否可消费,请用
poll()并检查返回值;若需预览且不能移除,得自己加读锁或改用其他结构 - 大量调用
peek()且队列活跃时,可能触发内部清理线程频繁工作,间接影响性能
和 CopyOnWriteArrayList 做队列用,哪个更合适
别这么比。CopyOnWriteArrayList 每次写都要复制整个数组,add() 是 O(n),get(0) 虽快但不是队列语义;它根本不是为队列场景设计的。
ConcurrentLinkedQueue 的优势在于「写多读少 + 高并发入/出」,而 CopyOnWriteArrayList 适合「读极多、写极少、迭代安全」的场景,比如监听器列表。
- 如果你发现代码里用
CopyOnWriteArrayList模拟队列(比如list.get(0)+list.remove(0)),立刻停下来——这是严重误用,性能差且线程安全假象 -
ConcurrentLinkedQueue的内存占用略高(每个节点含额外原子引用),但扩容零成本;CopyOnWriteArrayList写时内存翻倍,GC 压力大 - 两者都不保证迭代器强一致性,但
ConcurrentLinkedQueue.iterator()迭代期间遇到已出队节点,会跳过而非报错
真正难处理的是「需要优先级」或「带延迟」的队列,那得看 PriorityBlockingQueue 或 DelayedQueue,ConcurrentLinkedQueue 压根不碰这些事。











