文章目录
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
中的节点在没有引用指向时会被垃圾回收。因此,如果队列中的元素不再被引用,相应的节点也会被回收。