介绍
非阻塞队列。高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,通过 CAS 操作实现。
ConcurrentLinkedQueue实际对应的是LinkedList,是一个线程安全的无界队列,但LinkedList是一个双向链表,而ConcurrentLinkedQueue是单向链表。
ConcurrentLinkedQueue线程安全在于设置head、tail以及next指针时都用的cas操作,而且node里的item和next变量都是用volatile修饰,保证了多线程下变量的可见性。而ConcurrentLinkedQueue的所有读操作都是无锁的,所以可能读会存在不一致性。
如果对队列加锁的成本较高则适合使用无锁的 ConcurrentLinkedQueue 来替代。适合在对性能要求相对较高,同时有多个线程对队列进行读写的场景。
底层源码
初始属性
//head节点存放链表第一个item为null的节点,但tail则并不是总指向最后一个节点
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
//Node中主要包含两个属性, 节点元素( item )和指向下一个节 点( next )的引用
private static class Node<E> {
volatile E item;
volatile Node<E> next;
}
head和tail是volatile类型,具有 volatile 赋予的含义:“即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入”。
Node是单向链表节点,next指向下一个Node,item用于存储数据。Node中操作节点数据的API,是通过Unsafe机制的CAS函数实现的;例如casNext()是通过CAS函数“比较并设置节点的下一个节点”。
构造器
//新建了一个“内容为null的节点”,并设置表头head和表尾tail的值为新节点。
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
入队offer方法
public boolean offer(E e) {
// 检查e是不是null,是的话抛出NullPointerException异常。
checkNotNull(e);
// 创建新的节点
final Node<E> newNode = new Node<E>(e);
// 将“新的节点”添加到链表的末尾。
for (Node<E> t = tail, p = t;;) {//这个for循环是个死循环,增加了两个指针p,t。
Node<E> q = p.next;
// 情况1:q为空,p就是尾节点,新节点插入
if (q == null) {
// CAS操作:如果“p的下一个节点为null”(即p为尾节点),则设置p的下一个节点为newNode。
// 如果该CAS操作成功的话,则比较“p和t”(若p不等于t,则设置newNode为新的尾节点),然后返回true。
// 如果该CAS操作失败,这意味着“其它线程对尾节点进行了修改”,则重新循环。
if (p.casNext(null, newNode)) {
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
}
// 情况2:p和q相等
else if (p == q)
p = (t != (t = tail)) ? t : head;
// 情况3:其它\\
//这里是移动p指针,意思就是此时如果p不是最后一个元素则把p指针指向tail,否则指向q,也就是指向p.next元素。
else
p = (p != t && t != (t = tail)) ? t : q;
}
}
总结:
非阻塞队列中的几种主要方法:offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false; poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null; peek():获取队首元素,若成功,则返回队首元素;否则返回null对于非阻塞队列,
对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果
offer过程实际就是不停就是在单链表不断挂新元素的过程,挂next元素时使用了cas来保证线程的安全性,而且不是每次offer都会移动tail指针,而是tail已经不是现在的最后一个node时才会移动tail指针