ConcurrentLinkedQueue 是并发中的高性能队列,而 BlockingQueue 接口下的是并发的阻塞队列。
ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null 元素。
属性
头尾两个指针。
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
对于 Node 类,定义了一系列的 CAS 操作:
private static class Node<E> {
volatile E item;
volatile Node<E> next;
// 建立新节点
Node(E item) {
UNSAFE.putObject(this, itemOffset, item);
}
// 更新节点
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
// 延迟设置下一个节点
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
// 更新下一个节点
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
// 当前元素偏移量
private static final long itemOffset;
// 下一个元素的偏移量
private static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
itemOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
构造函数
// 创建一个最初为空的ConcurrentLinkedQueue
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
// 根据给定集合来建立ConcurrentLinkedQueue
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
// 遍历集合c中的每个元素
for (E e : c) {
checkNotNull(e);
// 对每个元素建立Node节点
Node<E> newNode = new Node<E>(e);
if (h == null)
// 如果是空链表,那么当前节点是第一个节点
h = t = newNode;
else {
// 如果已经有节点,在尾部加上新节点即可
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
重要方法
offer(E e)
将指定元素插入此队列的尾部。
public boolean offer(E e) {
// 检查对象是否为null
checkNotNull(e);
// 不为null就可以创建对应的Node节点
final Node<E> newNode = new Node<E>(e);
// 一直循环,直到节点新增成功
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) { // 分支1
// p是最后一个节点的情况,那么可以在其后新加节点
// CAS操作,如果为null的时候,则增加新节点
// 这里如果有另一个线程已经增加了新节点,p的后一个节点就不为null了,增加失败继续循环
// 将t节点的next置为当前节点
if (p.casNext(null, newNode)) { // 分支2
if (p != t) // 分支3
// 如果尾节点为t,那么更新为newNode
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
// 如果当前线程和另一个线程在上边步骤的casNext没有竞争过的话,继续循环执行即可
}
// 如果p和q相等,且都不为null
else if (p == q) // 分支4
// 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.
p = (t != (t = tail)) ? t : head;
else // 分支5
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
根据代码完整的添加几个节点,就知道增加的完整过程了:
1. 队列为空时,第一个元素 A 插入,此时头尾都是初始节点,分支 1 处显然成立,进入分支 2 新增一个节点,此时 p==t 指向同一个节点,不走分支 3。(此时 tail 未变,仍为初始节点,并没有指向新节点 A)。这里 return true,完成一次插入。
2. (1) 插入第二个元素 B,此时尾指针 tail 指向初始节点,而 q=p.next,即 q 指针指向 A 节点。此时显然不走分支 1 和 分支 4,直接走分支 5 ,我们看分支 5 的操作:此时 p 和 t 都指向初始节点,所以执行结果是 p=q ,即 p 指针也指向了 q 指针的位置,节点 A 处。
(2) 继续循环,由于 p=q,那么循环第一句将,q=p.next,p 指针为节点 A,那么 q 显然为 null 。此时分支 1 成立,进行分支 2 的 CAS 操作,显然可以成功添加节点 B 。此时,由于 t 还指向 tail 的初始节点,而 p 指针指向 A,所以二者不想等,进行分支 3 ,这是设置 tail 的 CAS 操作,显然可以成立,将 B 节点设置为 tail 尾节点。return true,完成第二次插入。
3. 插入第三个元素 C ,就和插入元素 A 一样了。
tail不是时刻指向最后一个节点,至少间隔1个节点,才会更新一次tail。
poll()
public E poll() {
// 内层的continue,跳到外层继续执行,是为了内层for循环的条件重置的
restartFromHead:
// 两层for循环均是无限循环直到成功return
for (;;) {
// 定义p,h指针指向head
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
// 如果元素不为空而且p处的元素为item的话,将其置为null
if (item != null && p.casItem(item, null)) { // 分支1
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // hop two nodes at a time // 分支2
updateHead(h, ((q = p.next) != null) ? q : p);
// 返回出队列的对象
return item;
}
else if ((q = p.next) == null) { // 分支3
updateHead(h, p);
return null;
}
else if (p == q) // 分支4
continue restartFromHead;
else // 分支5
p = q;
}
}
}
仍然根据代码完整的 poll 出几个节点,就知道增加的完整过程了:
比如当前队列中有 A , B , C 三个节点需要出队列。根据上边的入列过程,我们知道 C 节点进来后,tail 指针并没有更新,仍然是指向 B 节点的。(因为没有 D 节点,如果 D 节点入列了,才会将 tail 指向 D,这里可以看成 每入列两个节点 是一个完整的过程。)
1. (1)出列一个节点,定义 p , h 指针指向 head, 假如之前没有进行过出列操作,那么 head 指针是指向初始节点的,为 null,所以,分支 1 不成立,而 q=p.next 显然指向 A 节点不为 null ,分支 3 也不成立,显然分支 4 也不成立,只能走分支 5 ,p=q ,即 p 和 q 一样都指向节点 A 了。
(2)继续内层循环,由于 p 指向节点 A ,那么此时 分支 1 成立,将 A 节点 item 置为 null,此时 p 显然和 h 不相等,继续走分支 2,将 h 指针置为 B 节点。然后 return item,即 A 对象出队列。
2. 再出列一个节点,此时 p, h,head 指针 都指向节点 B,分支 1 成立,将 B 节点的 item 置为 null,此时 p 和 h 相等,跳过分支 2 ,return item,即 B 对象出队列。
3. (1)再出列一个节点,由于 head 指针并没有更新位置,仍然是在 B 节点的位置上(虽然 B 的对象已经出队列了),此时,p,h指针都指向 head,此节点 item 为 null,所以 分支1 不成立,分支 3 显然也不成立,此时 q 指向 C 节点。执行分支 5,将 p 也指向 C 节点。
(2)继续内层循环,由于 p,q 都指向 C 节点,进入分支 1,将 C 节点的 item 置为 null 。 然后判断分支 2,此时 p!=h,所以更新头结点,由于 C 节点没有下一个节点了,所以 head 指针更新到 C 节点上。之后 return,节点 C 出列。
4. 在出列一个节点,由于 head 指向 C 节点的位置,p,h 也一样,此时 分支 3 成立,return null。已经没有可出列的节点了。
关于入队和出队过程中未涉及到的 分支4,是这样的情景:
如果入队元素较少导致 tail 节点更新较慢,同时出队操作较快导致 head 已经指向 tail 之后的节点。这种情况下需要将 p 要么指向最新的 tail 节点(若 tail 节点已经更改),要么指向 head 节点,才能不落后于队列。
此队列利用了 CAS 来完成数据操作,同时允许队列的不一致性。