6.2 ConcurrentLinkedQueue
一个基于链接节点的无界线程安全队列
6.2.2 入队列
1、入队列的过程
1、添加元素1,队列更新head节点的next节点为元素1节点,tail节点默认情况下等于head节点
2、添加元素2,队列首先设置元素1节点的next节点为元素2节点,然后更新tail节点指向元素2节点
3、添加元素3,设置tail节点的next节点为元素3节点
4、添加元素4,设置元素3的next节点为元素4节点,然后将tail节点指向元素4节点
看下offer的例子
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; // 步骤1
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) { //步骤2
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK. //步骤5
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// 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; //步骤3
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q; //步骤4
}
}
为了方便理解这里用图来表示下 添加俩个元素的情况下,队列的变化情况 ( 每个步骤相应的源码在上面的源码块有标注,对照看)
根据以上代码可以看出,添加2次元素才会设置一次tail节点,从本质上来看通过增加对volatile变量的读操作来减少对volatile变量的写操作,所以入队效率会有所提升
6.2.3 出队列
要注意 不是每次弹出首节点都需要更新head,只有当head节点里面没有数据了才会去更新,减少CAS的使用
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
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
updateHead(h, ((q = p.next) != null) ? q : p); //步骤4
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p); //步骤2
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q; //步骤三
}
}
}
下面用图来表示下 源码的逻辑( 每个步骤相应的源码在上面的源码块有标注,对照看)