概述

ConcurrentLinkedQueue是一个单向链表结构的无界并发队列。通过CAS来实现并发安全。内存一致性遵循对ConcurrentLinkedQueue的插入插入操作先行于访问会或移除操作。

源码分析

继承体系

lzPOQP.png

重要属性

1
2
private transient volatile Node<E> head;
private transient volatile Node<E> tail;

ConcurrentLinkedQueue中只有两个属性。

重要方法分析

offer方法

offer方法用于向队列尾部添加节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public boolean offer(E e) {
checkNotNull(e);
//创建新的节点
final Node<E> newNode = new Node<E>(e);

for (Node<E> t = tail, p = t;;) {//自旋
Node<E> q = p.next; //p实际上指向尾节点
if (q == null) { //尾节点下一个为null
// p is last node
if (p.casNext(null, newNode)) { //CAS插入
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) //tail之后至少有两个节点才修改tail
casTail(t, newNode); // CAS替换尾节点
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)//p节点指向自身,说明p是自链节点
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
//如果tail节点被其它线程修改,此时需要从head节点开始向
//后遍历,因为从head开始可达所有的live节点
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
//继续向后查找,如果tail节点变化,重新获取tail。
p = (p != t && t != (t = tail)) ? t : q;
}
}

整个方法的执行流程如下:

  1. tail节点先后自旋查找next为null的节点,也就是最后一个节点(因为tail节点并不是每次都更新,所以我们取到tail节点可能并不是最后一个节点)
  2. 通过CAS插入新增节点

为什么tail可能不是指向最后一个节点呢?
因为其实并不是每次操作都会更新head/tail节点。而是使用了一个松弛阈值,具体的体现是在if (p != t)(p初始是等于tail的,p伴随着每次查找都会后移)。如果向后查找了一次以上,再加上新增的节点,说明tail之后有两个(或以上)的节点了,才会通过CAS更新tail

poll方法

poll方法移除队列中的头节点并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public E poll() {
restartFromHead:
for (;;) {
//从head开始先后查找第一个live节点
for (Node<E> h = head, p = h, q;;) {
E item = p.item;

if (item != null && p.casItem(item, null)) {
//找到第一个不为null的节点,通过CAS设置item为null
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // 跳两个以上的节点才修改head
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) { //队列已空
updateHead(h, p);//CAS修改head为p
return null;
}
else if (p == q) //p为自链节点,重新获取head循环
//跳转到restartFromHead继续循环
continue restartFromHead;
else
p = q; //先后查找
}
}
}

这个方法的流程:
从head开始向后查找第一个live(item不为null的节点)节点。通过CAS修改节点的live为null。返回当前的节点的item。这个地方也是跳两个以上节点时才会更新head