ConcurrentLinkedQueue 的实现 原理:是基于 CAS,通过head/ail指针记录队列头部和尾部。
首先,它是一个单向链表,定义如下。
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable {
//...
private static class Node<E> {
volatile E item;
volatile Node<E> next;
//..
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
...
在ConcurrentLinkedQueue中,head/tail的更新可能落后于节点的入队和出队,因为它不是直接对head/tail指针进行CAS操作的,而是对Node中的item 进行操作。下面进行详细分析。
上面入队其实是每次在队尾追加两个结点时,才移动一次tail节点,具体过程如下图所示
初始时,队列中有一个节点item,tail指向该节点,假设线程1要入队item2节点。
step1: p= tail, q= p.next = NULL
step2: 对p的next执行CAS操作,追加item2,成功之后,p= tail所以上面的casTail函数不会执行,直接返回。此时tail指针没有变化。
之后,假设线程2要入队item3节点,如下图所示。
step3: p= tail, q= p.next。
step4: q!= NULL,因此不会入队新节点。p, q都后移1位。因为执行了如下代码块
else{
p = (p != t && t != (t = tail)) ? t : q;//后移p指针
并且这个for是死循环,然后再次循环时,又执行了如下代码块
Node<E> q = p.next;
所以,p, q都后移1位。
step5: q=NULL, 对p的next执行CAS操作,入队item3节点。执行如下操作
if (p.casNext(null, newNode)) {
// 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.
return true;
}
step6: p!=t, 满足条件,执行上面的casTail操作,tail 后移2个位置,到达队列尾部。
最后总结一下入队列的两个关键点:
(1)即使tail指针没有移动,只要对p的next指针成功进行CAS操作,就算成功入队列
(2)只有当p!= tail的时候,才会后移tail 指针。也就是说,每连续追加2个节点,才后移1次tail指针。即使CAS失败也没关系,可以由下1个线程来移动tail指针。
上面说了入队列之后,tail指针不变化,那是否会出现入队列之后,要出队列却没有元素可出的情况呢?
出队列的代码和入队列类似,也有p、q2个指针,整个变化过程如图所示。假设初始的时候head指向空节点,队列中有iteml、item2、 item3 三个节点。
step1: p=head, q= p.next。p! =q。
step2:后移p指针,使得p=q。
step3:出队列。关键点:此处并没有直接删除iteml节点,只是把该节点的item通过CAS操作设置为了null
step4: p != head,此时队列中有了2个NULL节点,再前移1次head指针,对其执行updateHead操作。
最后总结一下出队列的关键点:
(1)出队列的判断并非观察tail 指针的位置,而是依赖于head指针后续的节点是否为NULL这一条件。
(2)只要对节点的item 执行CAS操作,置为NULL成功,则出队列成功。即使head指针没有成功移动,也可以由下一个线程继续完成。
因为head/tail并不是精确地指向队列头部和尾部,所以不能简单地通过比较head/tail指针来判断队列是否为空,而是需要从head指针开始遍历,找第1个不为NULL的节点。如果找到,则队列不为空:如果找不到,则队列为空。代码如下所示。
在我们执行poll方法中,有如下一段代码
if (item != null && p.casItem(item, null)) {
// 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);
return item;
}
这个updateHead方法在这里面扮演什么样的角色呢?
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
可以看到,在updateHead中将p作为新的链表头部(通过casHead()实现),而原有的,head就被设置为哨兵(通过lazySetNext方法实现)。
这样一个哨兵节点就产生了,而由于此时原有的head头部和tail实际上是同一个元素。因此,再次用offer()方法插入元素时,就会遇到这个 tail,也就是哨兵。
哨兵节点:就是next指向自己的节点,价值不大,主要表示要删除的节点,或者空节点
大家可以自己用debug进行调试,以后分析