JUC——并发容器ConcurrentLinkedQueue源码解读

文章目录

  • 1:ConcurrentLinkedQueue的介绍
    • 1.1 初始化分析
    • 1.2 入队列分析
    • 1.3 出队列情况分析
    • 1.4 队列判空分析
    • 1.5 updateHead方法分析
  • 2:使用debug进行源码分析

1:ConcurrentLinkedQueue的介绍

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 进行操作。下面进行详细分析。

1.1 初始化分析

JUC——并发容器ConcurrentLinkedQueue源码解读_第1张图片

1.2 入队列分析

JUC——并发容器ConcurrentLinkedQueue源码解读_第2张图片
上面入队其实是每次在队尾追加两个结点时,才移动一次tail节点,具体过程如下图所示
初始时,队列中有一个节点item,tail指向该节点,假设线程1要入队item2节点。
step1: p= tail, q= p.next = NULL
step2: 对p的next执行CAS操作,追加item2,成功之后,p= tail所以上面的casTail函数不会执行,直接返回。此时tail指针没有变化。
JUC——并发容器ConcurrentLinkedQueue源码解读_第3张图片
之后,假设线程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个位置,到达队列尾部。
JUC——并发容器ConcurrentLinkedQueue源码解读_第4张图片

最后总结一下入队列的两个关键点:

(1)即使tail指针没有移动,只要对p的next指针成功进行CAS操作,就算成功入队列
(2)只有当p!= tail的时候,才会后移tail 指针。也就是说,每连续追加2个节点,才后移1次tail指针。即使CAS失败也没关系,可以由下1个线程来移动tail指针。

1.3 出队列情况分析

上面说了入队列之后,tail指针不变化,那是否会出现入队列之后,要出队列却没有元素可出的情况呢?
JUC——并发容器ConcurrentLinkedQueue源码解读_第5张图片

出队列的代码和入队列类似,也有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操作。

JUC——并发容器ConcurrentLinkedQueue源码解读_第6张图片

最后总结一下出队列的关键点:

(1)出队列的判断并非观察tail 指针的位置,而是依赖于head指针后续的节点是否为NULL这一条件。

(2)只要对节点的item 执行CAS操作,置为NULL成功,则出队列成功。即使head指针没有成功移动,也可以由下一个线程继续完成。

1.4 队列判空分析

因为head/tail并不是精确地指向队列头部和尾部,所以不能简单地通过比较head/tail指针来判断队列是否为空,而是需要从head指针开始遍历,找第1个不为NULL的节点。如果找到,则队列不为空:如果找不到,则队列为空。代码如下所示。
JUC——并发容器ConcurrentLinkedQueue源码解读_第7张图片

1.5 updateHead方法分析

在我们执行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指向自己的节点,价值不大,主要表示要删除的节点,或者空节点

如下图
JUC——并发容器ConcurrentLinkedQueue源码解读_第8张图片

2:使用debug进行源码分析

大家可以自己用debug进行调试,以后分析

你可能感兴趣的:(Java高并发,juc,并发编程,源码)