文章目录
ConcurrentLinkedQueue
ConcurrentLinkedQueue 是Java中的一个并发队列实现,是一个基于链接节点(链表)的无界的线程安全非阻塞队列,适用于多线程环境下的并发访问,即多个线程访问同一个 collection 的场景。
ConcurrentLinkedQueue 采用了无锁 (lock-free)的算法实现,并且基于链表数据结构。它支持高效的并发插入和删除操作,以及无界的队列长度。
java
public class ConcurrentLinkedQueue<E>
extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable {}
ConcurrentLinkedQueue 继承了抽象类 AbstractQueue,实现了Queue 接口、Serializable 接口,表示其可以被序列化。
实现原理
ConcurrentLinkedQueue 是基于链表实现的,每个链表节点包含数据域 (item)和指针域 (next)。其中,item 用于存储数据,next 指向下一个节点。节点的 item 和 next 属性都是用 volatile 修饰的,保证了其可见性。
内部节点
ConcurrentLinkedQueue 内部类 Node 类表示链表结点,用于存放元素,包含 item域 和 next域,item域 表示元素,next域 表示下一个结点,其利用了反射机制和CAS机制来更新 item域 和 next域,保证原子性。
ConcurrentLinkedQueue 内部类 Node 的源代码

特点
-
非阻塞算法 :
ConcurrentLinkedQueue使用非阻塞算法实现,它允许多个线程同时进行插入和删除操作,而不会发生死锁或线程阻塞 。使用了反射机制和CAS机制来更新头节点和尾结点,保证原子性 -
无界队列 :
ConcurrentLinkedQueue没有固定的容量限制,可以根据需要动态扩容 ,由于没有容量限制,所以插入操作永远不会被阻塞。 -
FIFO 排序 :
ConcurrentLinkedQueue使用先进先出(FIFO)的行为模式,保证先插入的元素会先进行取出操作。队列的头部是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部。
-
线程安全 :
ConcurrentLinkedQueue是线程安全的,多个线程可以同时进行插入和删除操作而不需要额外的同步措施。 -
支持高并发 :
ConcurrentLinkedQueue在高并发环境下表现优异,可以有效处理多线程同时对队列进行操作的情况,使用 CAS(Compare And Swap)操作来保证线程的安全性。 -
不支持阻塞操作 :
ConcurrentLinkedQueue不支持阻塞操作 ,如果队列为空时调用poll()方法将返回 null 而不是阻塞等待。 -
迭代器弱一致性 :由于
ConcurrentLinkedQueue是并发数据结构,其迭代器的遍历顺序可能不一致或发生变化。因此在迭代时需要注意,并尽量避免修改队列。 -
ConcurrentLinkedQueue不允许插入 null 元素
常用方法
ConcurrentLinkedQueue 类提供了一系列方法用于操作并发链表队列。以下是常用的方法:
-
add(E e):将指定元素添加到队列的尾部,如果队列已满,则抛出IllegalStateException异常。 -
offer(E e):将指定元素添加到队列的尾部,并返回是否成功。如果队列已满,返回 false。 -
poll():移除并返回队列头部的元素,如果队列为空,则返回null。 -
remove():移除并返回队列头部的元素,如果队列为空,则抛出NoSuchElementException异常。remove()是基于 Iterator 实现 的,如果在迭代期间使用remove()来删除元素,则可能导致元素遗漏或重复删除的问题。通常情况下,建议使用
poll()或take()方法来安全地获取队列元素并保证程序的稳定性。 -
peek():返回队列头部的元素,如果队列为空,则返回null。 -
element():返回队列头部的元素,如果队列为空,则抛出NoSuchElementException异常。 -
size():返回队列中的元素个数。 -
isEmpty():判断队列是否为空。 -
contains(Object o):判断队列是否包含指定元素。 -
toArray():返回包含队列所有元素的数组。 -
clear():清空队列,将队列置为空。
构造方法
- 创建一个空的 ConcurrentLinkedQueue。
java
public ConcurrentLinkedQueue() {
// 初始化头节点与尾结点
head = tail = new Node<E>(null);
}
- 创建一个包含指定集合元素的 ConcurrentLinkedQueue,按照集合的迭代顺序进行插入。
java
ConcurrentLinkedQueue(Collection<? extends E> collection)
延迟更新策略
如果让 tail 永远作为队列的队尾节点,大量的入队操作会导致每次都要执行 CAS 进行 tail 的更新,十分损耗性能。为优化性能,使用了延迟更新策略。
在更新操作时,源码中会有注释为:hop two nodes at a time ,每间隔 1 次(tail 和 队尾节点 的距离为 1)进行才利用 CAS 更新 tail。对于 head 同理。虽然这样设计会多出在循环中定位队尾节点,但总体来说读的操作效率要远远高于写的性能。
通过分析 ConcurrentLinkedQueue 中 offer、poll 的源码,发现tail 尾节点 和 head 头节点 是延迟更新的,两者更新触发时机为:
-
tail更新触发时机:当tail指向的节点的下一个节点为null的时候,只插入节点不更新tail。当
tail指向的节点的下一个节点不为null的时候,会执行定位队列真正的队尾节点的操作,找到队尾节点后完成插入之后才会通过casTail()进行tail更新。 -
head更新触发时机: 当head指向的节点的item域不为 null 的时候,只删除节点不更新head。当
head指向的节点的item域为 null 的时候,会执行定位队列真正的队头节点的操作,找到队头节点后完成删除之后才会通过updateHead()进行head更新。
使用场景
ConcurrentLinkedQueue 特别适用于以下场景:
-
高并发场景:由于 ConcurrentLinkedQueue 采用了无锁编程技术,它在高并发环境下的性能表现非常出色。
-
需要快速插入和删除的场景:队列的头部和尾部都可以进行快速的插入和删除操作。
-
无界队列场景:与有界队列不同,ConcurrentLinkedQueue 可以存储任意数量的元素,适用于不需要限制队列大小的场景。
ConcurrentLinkedQueue 使用示例
以下是一个简单的 ConcurrentLinkedQueue 使用示例,展示了如何创建一个队列,并在其上执行基本的操作:
java
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentLinkedQueueExample {
public static void main(String[] args) {
// 创建 ConcurrentLinkedQueue
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
// 添加元素
queue.offer("Apple");
queue.offer("Banana");
queue.offer("Cherry");
// 打印队列
System.out.println("队列初始状态: " + queue);
// 创建线程向队列中添加元素
Thread addThread = new Thread(() -> {
queue.offer("Durian");
queue.offer("Elderberry");
System.out.println("添加元素后的队列: " + queue);
});
// 创建线程从队列中取出元素
Thread pollThread = new Thread(() -> {
while (!queue.isEmpty()) {
String item = queue.poll();
if (item != null) {
System.out.println("取出元素: " + item);
}
}
});
// 启动线程
addThread.start();
pollThread.start();
try {
// 等待线程结束
addThread.join();
pollThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("主线程被中断");
}
// 打印最终队列状态
System.out.println("最终队列状态: " + queue);
}
}
注意事项
在使用 ConcurrentLinkedQueue 的过程中,应注意以下问题:
- 弱一致性 :
ConcurrentLinkedQueue提供的是弱一致性保证,这意味着如果存在多个线程并发操作队列,队列的顺序可能会发生变化。 - 非阻塞特性 :
ConcurrentLinkedQueue的poll方法在队列为空时会立即返回null,而不是阻塞等待。这意味着如果你需要等待队列中有元素,应该考虑使用BlockingQueue。 - 迭代器快照 :
ConcurrentLinkedQueue的iterator方法返回的是一个快照迭代器,不会反映迭代期间队列的变化。如果需要实时迭代队列中的元素,可以考虑使用其他机制,如轮询。 - 内存回收 :
ConcurrentLinkedQueue中的节点在没有引用指向时会被垃圾回收。因此,如果队列中的元素不再被引用,相应的节点也会被回收。