原子类如atomicreference通过cas实现无锁栈和队列,避免阻塞与死锁;无锁栈基于链表与栈顶原子引用,push/pop均用cas重试;无锁队列采用michael-scott算法,维护head/tail双原子引用并支持协助推进;需警惕aba、节点复用、可见性及调试难题,生产环境优先选用concurrentlinkedqueue等jdk成熟实现。

Java 并发编程中,原子类(如 AtomicReference)是实现无锁(lock-free)数据结构的核心工具。无锁栈和无锁队列不依赖 synchronized 或 ReentrantLock,而是通过 CAS(Compare-And-Swap)操作保证线程安全,避免阻塞和死锁,提升高并发场景下的吞吐量。
用 AtomicReference 实现无锁栈
无锁栈通常基于链表实现,每个节点包含数据和指向下一个节点的引用。核心是用 AtomicReference 管理栈顶指针,所有 push/pop 操作都通过 CAS 原子更新。
- 定义栈节点:每个节点持有
item和next引用,next用volatile修饰或由原子引用间接保证可见性 - push 操作:构造新节点 → 读取当前栈顶 → CAS 设置新节点为新栈顶,失败则重试
- pop 操作:读取当前栈顶 → CAS 将栈顶更新为
top.next,同时返回原栈顶节点;若栈为空(top == null),返回 null - 注意 ABA 问题:虽然栈结构对 ABA 敏感度较低(节点对象不可重用),但若允许节点复用,建议搭配
AtomicStampedReference加版本号
用 AtomicReference 实现无锁单向队列(Michael-Scott 队列)
经典无锁队列采用 Michael-Scott 算法,维护 head 和 tail 两个原子引用。入队在 tail 后插入,出队从 head 后移除,二者可异步推进,减少竞争。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 队列节点需包含
item和next字段,初始next为 null - 入队(offer):创建新节点 → CAS 更新 tail 的 next 指向新节点 → 再 CAS 更新 tail 自身;若中间被其他线程抢先,需先帮助推进 tail(“helping”)
- 出队(poll):读取 head → 若 head.next 不为 null,则 CAS 将 head 向后移动一位并返回 head.next.item;否则说明队列空或 tail 尚未更新,需协助修正 head/tail 对齐
- head 和 tail 初始指向同一个哨兵节点(dummy node),避免空队列边界判断复杂化
关键注意事项与常见陷阱
无锁结构看似简洁,但极易因细微错误导致无限循环、丢失更新或内存泄漏。
- 必须用
new Node(...)创建新节点,不能复用已出队节点(除非严格管控生命周期并处理 ABA) - CAS 失败必须重试(loop + compareAndSet),不能直接抛异常或忽略
- 节点的
next字段即使设为 null,也要确保对其他线程可见(final 或 volatile 修饰;JDK 9+ 中VarHandle更精准) - 避免在 CAS 循环中做耗时操作(如 I/O、复杂计算),否则影响公平性和响应性
- 调试困难:推荐配合 JMH 做并发压测,并用
Unsafe或 JOL 工具检查对象布局和缓存行对齐(防 false sharing)
实际开发中更推荐的方式
手写无锁栈/队列适合学习和特定高性能场景,但生产环境建议优先使用 JDK 提供的成熟实现:
-
java.util.concurrent.ConcurrentLinkedQueue:基于 Michael-Scott 的无锁队列,经过充分验证 -
java.util.concurrent.ConcurrentLinkedDeque:无锁双端队列,支持栈式 LIFO 操作 - 若需阻塞语义,可用
LinkedBlockingQueue(基于锁)或TransferQueue(如SynchronousQueue) - Java 17+ 可关注
StructuredTaskScope和虚拟线程,降低对底层无锁结构的依赖
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










