(juc Series) source code of concurrent linked queue of concurrent collection

The source code of this article is based on JDK13

ConcurrentLinkedQueue

Official annotation translation

An unbounded thread safe queue implemented with a linked list. This queue provides the element order of FIFO

When multiple threads need to share access to a collection, ConcurrentLinkedQueue is an appropriate choice. Like other concurrent collection implementations, this class does not accept null elements

This implementation uses an efficient lock free algorithm. It comes from paper Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms

The iterator is weakly synchronous, and the returned element is the element snapshot when the iterator is created. The ConcurrentModificationException will not be thrown. It can be carried out concurrently with other operations. The element when the iterator is created will be returned exactly once

It should be noted that, unlike most other collections, the size method is not a constant time operation. Because of the asynchronous nature of the queue, counting the current elements requires traversing all elements. Therefore, if other threads are changing, the size method may return inaccurate numbers

Batch operations do not guarantee atomicity, such as addAll. When foreach and addAll run together, foreach may only observe some elements

This class and its Iterator implement all the optional methods of Queue and Iterator

Source code

definition

public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable {

This is a queue

struct Node

    static final class Node<E> {
        volatile E item;
        volatile Node<E> next;

        /**
         * Constructs a node holding item.  Uses relaxed write because
         * item can only be seen after piggy-backing publication via CAS.
         */
        Node(E item) {
            ITEM.set(this, item);
        }

        /** Constructs a dead dummy node. */
        Node() {}

        void appendRelaxed(Node<E> next) {
            // assert next != null;
            // assert this.next == null;
            NEXT.set(this, next);
        }

        boolean casItem(E cmp, E val) {
            // assert item == cmp || item == null;
            // assert cmp != null;
            // assert val == null;
            return ITEM.compareAndSet(this, cmp, val);
        }
    }

It saves the data Item of the current node and the pointer to the next node next

Two cas methods are provided to change data and pointer respectively

attribute

    transient volatile Node<E> head;

    private transient volatile Node<E> tail;

Saved the head and tail nodes of the linked list

Construction method

    public ConcurrentLinkedQueue() {
        head = tail = new Node<E>();
    }

    public ConcurrentLinkedQueue(Collection<? extends E> c) {
        Node<E> h = null, t = null;
        for (E e : c) {
            Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
            if (h == null)
                h = t = newNode;
            else
                t.appendRelaxed(t = newNode);
        }
        if (h == null)
            h = t = new Node<E>();
        head = h;
        tail = t;
    }

Two construction methods are provided to support the creation of empty queues and the initialization of all given collections into queues

Join method offer

    public boolean add(E e) {
        return offer(e);
    }

    public boolean offer(E e) {
        // Create a new node
        final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));

        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            // The next of the tail node is empty. The cas is updated directly and successfully
            if (q == null) {
                // p is last node
                if (NEXT.compareAndSet(p, 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; failure is OK
                        TAIL.weakCompareAndSet(this, t, newNode);
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            // The p node is deleted, that is, out of the queue. Reset the value of the p node
            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;
            else
                // Check for tail updates after two hops.
                // If the tail node is updated elsewhere, see whether you should continue to look back or not
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

The offer method performs the actual addition operation, linking the given node to the tail of the existing queue. In the process, full consideration should be given to the competition with other threads

Outgoing method poll

    public E poll() {
        restartFromHead: for (;;) {
            for (Node<E> h = head, p = h, q;; p = q) {
                // Start at the head of the team
                final E item;
                // If the team leader is OK, directly update the cas and return the result
                if ((item = p.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;
                }
                // The next header node is empty and the queue is empty
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                // The current node is out of the team. Start from the team head again
                else if (p == q)
                    continue restartFromHead;
            }
        }
    }

Start traversing from the queue head. If the header node is successfully obtained and the CAS update is successful, it will return. Otherwise, continue to find the next node

View team leader peek

    public E peek() {
        restartFromHead: for (;;) {
            for (Node<E> h = head, p = h, q;; p = q) {
                final E item;
                // Queue header element OK. Returns the queue header element
                if ((item = p.item) != null
                    || (q = p.next) == null) {
                    updateHead(h, p);
                    return item;
                }
                // The current node is out of the queue. Find the team leader again
                else if (p == q)
                    continue restartFromHead;
            }
        }
    }

Relatively simple, keep trying to get the team head element

View quantity size

    public int size() {
        restartFromHead: for (;;) {
            int count = 0;
            // Start counting from the head of the team
            for (Node<E> p = first(); p != null;) {
                if (p.item != null)
                    if (++count == Integer.MAX_VALUE)
                        break;  // @see Collection.size()
                // The current element is out of the queue. Count from the beginning
                if (p == (p = p.next))
                    continue restartFromHead;
            }
            
            return count;
        }
    }

Count from the head of the team every time. If the middle and double opening are changed by others, count from the head of the team again

summary

A non - blocking, thread - safe queue with no lock in the whole process is implemented by CAS + spin

Reference articles

End.

Contact me

Finally, welcome to my personal official account, Yan Yan ten, which will update many learning notes from the backend engineers. I also welcome direct official account or personal mail or email to contact me.

The above are all personal thoughts. If there are any mistakes, please correct them in the comment area.

Welcome to reprint, please sign and keep the original link.

Contact email: huyanshi2580@gmail.com

For more study notes, see personal blog or WeChat official account, Yan Yan ten > > Huyan ten

Posted on Wed, 10 Nov 2021 14:08:56 -0500 by sherry