ConcurrentLinkedQueue source code analysis of concurrent programming

foreword

Today we continue to analyze the source code of java concurrent package. Who is the protagonist today? ConcurrentLinkedQueue, last time we analyzed CopyOnWriteArrayList, an alternative to concurrent ArrayList. This time, we analyzed ConcurrentLinkedQueue, the alternative to Concurrent LinkedArrayList, that is, concurrent linked list.

Demo

Demo

The class inheritance structure is as follows:

inheritance diagram

This class is an implementation under the Collection framework. That is, the data structure provided by the Java class library.

The add method inserts the specified element into the tail of this queue. The poll method gets and removes the head of this queue, or returns null if this queue is empty. The peek method gets but does not remove the head of this queue; returns null if this queue is empty.

So let's see how doug lea achieves concurrency safety. Before that, we can imagine that there are only two ways to achieve concurrency security, one is lock, just like the containers we analyzed before, such as concurrentHashMap, CopyOnWriteArrayList, LinkedBolckingQueue, and the other is CAS, which is also used in these containers . So, if we were to implement this queue, what method would we use? Interesting question.

Let's start looking at the source code.

Add method source code analysis

In fact, the offer method is called, the add method is a container method specified by the Collection interface, and the offer method is a method of the Queue interface.

add method

Then let's look at the offer method:

    public boolean offer(E e) {
        // 检查是否是null,如果是null ,抛出NullPointerException
        checkNotNull(e);
        // 创建一个node 对象,使用  CAS 创建对象
        final Node<E> newNode = new Node<E>(e);
        // 轮询链表节点,知道找到节点的 next 为null,才会进行赋值
        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            if (q == null) {
                // 找到null值之后将刚刚创建的值通过CAS放入
                if (p.casNext(null, newNode)) {
                    // 因为 p 遍历在轮询后会变化,因此需要判断,如果不相等,则使用CAS将新节点作为尾部节点。
                    if (p != t)
                        casTail(t, newNode);  // Failure is OK.
                     // 放入成功后返回 ture
                    return true;
                }
            }
            // 轮询后  p 有可能等于 q,此时,就需要对 p 重新赋值。
            else if (p == q)
                // 这里需要注意一下:判断t != t,是因为并发下可能 tail 被改了,如果被改了,则使用新的 t,否则从链表头重新轮询。
                p = (t != (t = tail)) ? t : head;
            else
                // 同样,当 t 不等于 p 时,说明 p 在上面被重新赋值了,并且 tail 也被别的线程改了,则使用新的 tail,否则循环检查p的下个节点
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

The number of lines of code is very small, and the landlord's comment has also been written. Here we can see that doug lea uses CAS to prevent concurrency errors. At the same time, we can also see the concern about the modification of the tail variable. Through the judgment of t != t, to Check whether tail has been modified by other threads, and this offer operation, if unsuccessful, will never return, and the queue is also unbounded. This point needs to be paid attention to when using it.

So how does the poll method work?

Poll method source code analysis

    public E poll() {
        // 循环跳出标记,类似goto
        restartFromHead:
        // 死循环
        for (;;) {
            // 死循环,从 head 开始遍历
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;
                // 如果 head 不是null 且 将 head 的 item 属性设置为null成功,则返回并更新头节点
                if (item != null && p.casItem(item, null)) {
                    // 如果 p != h 说明在 p 轮询时被修改了
                    if (p != h) 
                         // 如果p 的next 属性不是null ,将 p 作为头节点,而 q 将会消失
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                // 如果 p(head) 的 next 节点 q 也是null,则表示没有数据了,返回null,则将 head 设置为null
                // 注意:  updateHead 方法最后还会将原有的 head 作为自己 next 节点,方便offer 连接。
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                // 如果 p == q,说明别的线程取出了 head,并将 head 更新了。就需要重新开始
                else if (p == q)
                    // 从头开始重新循环
                    continue restartFromHead;
               // 如果都不是,则将 h 的 next 赋给 h,并重新循环。
                else
                    p = q;
            }
        }
    }

The landlord has written a comment above, but there is a doubt that bothers the landlord, that is, else if (p == q) this line of code, there is no problem in the landlord's analysis, but when the landlord's single thread tests this code, it appears The strange situation cannot be explained. Therefore, the landlord posted a test case for everyone to take a look at:

Test code:

Breakpoint code:

Note that the breakpoint location must be the same as mine. There will be some strange effects. The landlord can't explain it. Because of this problem, the landlord has never dared to post this article, but the landlord feels it is necessary to speak out about this problem and to attract others.

The question is: how does a single thread get into this code? It makes sense, but this is not the case with threads.

Summarize

The source code analysis this time made the landlord very painful, and many articles on the Internet could not explain why. I hope someone skilled can tell the landlord what is going on?

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326093447&siteId=291194637