不能直接构建无锁队列,因jsr-166未提供裸构造接口;所有线程安全队列如concurrentlinkedqueue均基于cas+volatile+自旋+内存屏障封装实现,非可拼装积木。

不能直接“构建无锁队列”——JSR-166 里没有提供裸的无锁队列构造接口,所有线程安全队列(如 ConcurrentLinkedQueue)都是基于 CAS + volatile + 自旋+内存屏障实现的,但它们不是“可自由拼装”的积木,而是完整封装的成品。
为什么不能自己照着 AQS 写个无锁队列
很多人看到 AbstractQueuedSynchronizer 就想复用它的队列结构来造轮子,但这是错的路径:AQS 的 FIFO 队列是为**阻塞同步语义**服务的(线程挂起/唤醒),而真正无锁(lock-free)数据结构(如 ConcurrentLinkedQueue)用的是 Michael-Scott 算法,核心是双指针(head/tail)+ CAS + 多重校验,和 AQS 的节点入队/出队逻辑完全不同。
-
AQS的队列节点必须带线程引用、等待状态、前驱后继指针,用于 park/unpark 调度;无锁队列节点只存业务数据和 next 指针 -
AQS入队失败会自旋+阻塞;无锁队列入队失败只重试,绝不阻塞 - 你无法从
AQS中提取出一个“通用无锁链表基类”——它不暴露底层 node 操作,也不允许你绕过 state 控制
生产环境该用哪个无锁队列:ConcurrentLinkedQueue vs LinkedTransferQueue
Java 标准库中真正符合 lock-free 定义(即任意线程崩溃不影响其他线程进展)的队列只有两个:ConcurrentLinkedQueue 和 LinkedTransferQueue。选哪个取决于场景:
- 纯异步投递、不关心消费者是否就绪 → 用
ConcurrentLinkedQueue:吞吐高、无锁、无阻塞、不支持超时/中断 - 需要“手递手”传递(比如生产者希望等消费者拿到才返回)→ 用
LinkedTransferQueue:它混合了无锁 + 阻塞两种模式,transfer()会阻塞直到配对成功,offer()则退化为无锁写入 - 别碰
ArrayBlockingQueue或LinkedBlockingQueue:它们内部用ReentrantLock+Condition,本质是互斥锁,不是无锁
ConcurrentLinkedQueue 的典型误用与修复
常见错误不是“不会写”,而是没理解它的弱一致性语义:
- 调用
size()返回的值可能严重滞后——它要遍历整个链表,而链表在并发修改中不断变化;生产代码中应避免依赖size()做判断,改用空检查(isEmpty()相对可靠)或外部计数器 -
poll()和peek()在空队列时返回null,但注意:如果节点刚被消费线程 CAS 删除、但 next 指针尚未刷新,peek()可能短暂返回已逻辑删除的节点(JDK 9+ 已优化,但老版本仍存在) - 不要试图用
iterator()做批量处理——它不保证原子快照,遍历时可能跳过新入元素或重复看到旧元素;如需强一致性,应改用有界缓冲区(如RingBuffer)或加读锁的临时拷贝
真要定制无锁结构?绕不开 Unsafe + volatile + CAS 模式
如果你确实需要非标准行为(例如带 TTL 的无锁队列、按优先级合并的无锁堆),JSR-166 不提供捷径。你得直接操作底层原语:
- 节点字段必须声明为
volatile(如volatile Node next),不可用普通引用 - CAS 操作统一走
Unsafe.compareAndSwapObject()或VarHandle.compareAndSet()(JDK 9+ 推荐) - 每次 CAS 失败后必须重新读取最新值(尤其是 head/tail),不能复用旧本地变量
- 必须插入
Unsafe.fullFence()或Thread.onSpinWait()(JDK 9+)缓解 CPU 空转,否则在高争用下会打满核
这类代码极易出错,且不同 JDK 版本的内存模型表现可能微异——除非已有成熟论文(如 MS queue、Dekker queue)支撑,否则不建议在核心链路中自行实现。











