上个月,我的朋友阿星去面试了一家知名互联网大厂,回来就和我吐槽:“这年头社招面试太卷了!我一面直接问我ConcurrentLinkedQueue的源码,看得我头皮发麻……”
听到这里,我拍了拍阿星的肩膀,笑道:“别慌,这东西看似复杂,其实只要抓住核心逻辑,完全可以手撕源码!”于是,我给他讲了一遍ConcurrentLinkedQueue的实现原理,结果他二面的时候,侃侃而谈,直接拿下了Offer!
今天,我就把这次分享的内容整理出来,给各位Java工程师们分享一波“并发队列ConcurrentLinkedQueue的核心原理与源码解析”。这篇文章适用于有一定Java并发基础的同学,建议大家先了解CAS和Unsafe的基本概念,再来看这篇文章,会更有收获!
并发队列ConcurrentLinkedQueue简介在Java的并发编程里,我们经常会遇到高并发环境下的队列需求,比如高性能的任务调度、生产者消费者模型等。在这种情况下,普通的LinkedList显然无法满足需求,因为它的操作不是线程安全的。如果加synchronized锁,性能又会大打折扣。因此,JDK提供了一种无锁(lock-free)、基于CAS(Compare-And-Swap)实现的高效并发队列——ConcurrentLinkedQueue。
ConcurrentLinkedQueue的特点无锁:使用CAS(乐观锁)机制来保证并发安全,不会因为锁竞争导致线程阻塞。
非阻塞:它不会因为某个线程执行慢而阻塞其他线程,保证高吞吐量。
高效:适用于高并发场景,避免了synchronized带来的性能问题。
FIFO(先进先出):它是一个基于链表的队列,符合FIFO的特性,保证元素的入队和出队顺序。
ConcurrentLinkedQueue的数据结构我们先来看ConcurrentLinkedQueue的数据结构,它的核心是一个单向链表,主要包含两个重要的指针:
head(头指针):指向队列的第一个有效节点
tail(尾指针):指向队列的最后一个节点
这里使用了volatile关键字,保证可见性,使得多个线程可以正确地访问这些变量的最新值。
Node内部结构
队列中的每个元素被封装成一个Node节点,Node是一个静态内部类,结构如下:
注意:
item存储实际数据,可以为空(表示该节点已删除,但还未被垃圾回收)。
next指向下一个节点,用于连接链表结构。
入队(offer方法)源码解析当我们调用offer(E e)方法时,队列会将新元素追加到队尾。由于是无锁队列,这里主要依赖CAS来保证线程安全。让我们看看源码:
offer方法的执行逻辑
创建新节点:new Node<>(e)
遍历找到队尾:
p表示当前遍历的节点,初始值是tail(尾指针)。
q = p.next,如果q==null,说明p是当前的最后一个节点。
CAS插入新节点:
p.casNext(null, newNode):如果p.next还是null,就把新节点挂上去,入队成功。
casTail(t, newNode):成功后,CAS更新tail指向新节点。
如果失败(队尾已经改变),则重新尝试入队。
出队(poll方法)源码解析当我们调用poll()方法时,队列会返回并删除头部的元素。我们来看下源码:
poll方法的执行逻辑
从head开始遍历
尝试CAS清空item,如果成功,则返回item,并尝试更新head指针。
如果当前节点为空或已经被删除,继续向下遍历。
关键技术点1. CAS(Compare-And-Swap)
ConcurrentLinkedQueue是基于无锁(Lock-Free)机制实现的,核心就是CAS(比较并交换)。它的底层依赖Unsafe类,直接操作内存。
例如,队列的casNext方法是这样实现的:
2. 为什么用CAS而不是锁?
CAS是乐观锁,不会阻塞线程,性能更高。
synchronized会导致线程竞争,而CAS可以利用CPU指令级别的原子操作,避免锁开销。
适用场景高并发队列:如任务调度、生产者-消费者模型。
消息队列:可以用作简易的消息队列。
Web请求处理:如WebSocket的消息缓冲队列。
总结今天,我们深入剖析了ConcurrentLinkedQueue,包括:
基本结构(基于链表的无锁队列)
核心方法(offer、poll)
底层原理(CAS + Unsafe)
适用场景(高并发环境)
如果你在社招面试中遇到类似问题,现在是不是可以自信应对了呢?
END如果你觉得这篇文章有帮助,欢迎分享给你的朋友,让更多人学会这个重要的面试知识点!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!