(juc Series) concurrent linkeddeque source code of concurrent collection

The source code of this article is based on JDK13

ConcurrentLinkedDeque

Official annotation translation

An unbounded, concurrent, double ended queue is implemented using a linked list. Concurrent write, remove, and access operations between multiple threads can ensure security. When many threads share a common set, ConcurrentLinkedDeque is a good choice. Like other concurrent sets, this class does not accept null elements

Iterators are weakly consistent

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 ConcurrentLinkedDeque<E>
    extends AbstractCollection<E>
    implements Deque<E>, java.io.Serializable {

A two - terminal queue

Internal linked list node

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

The pointer of the front and back nodes and the element of the current node

attribute

    private transient volatile Node<E> head;

    private transient volatile Node<E> tail;

Saved head and tail nodes

Construction method

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

    public ConcurrentLinkedDeque(Collection<? extends E> c) {
        // Copy c into a private chain of Nodes
        Node<E> h = null, t = null;
        for (E e : c) {
            Node<E> newNode = newNode(Objects.requireNonNull(e));
            if (h == null)
                h = t = newNode;
            else {
                NEXT.set(t, newNode);
                PREV.set(newNode, t);
                t = newNode;
            }
        }
        initHeadTail(h, t);
    }

Two construction methods, one to construct an empty queue and one to initialize a given set into the queue

Team entry method

    public void addFirst(E e) {
        linkFirst(e);
    }

    public void addLast(E e) {
        linkLast(e);
    }

    public boolean offerFirst(E e) {
        linkFirst(e);
        return true;
    }

    public boolean offerLast(E e) {
        linkLast(e);
        return true;
    }

Support the addition of queue head and tail, specifically calling linkFirst and linkLast

  • linkFirst
    private void linkFirst(E e) {
        // Create current node
        final Node<E> newNode = newNode(Objects.requireNonNull(e));

        restartFromHead:
        for (;;)
            for (Node<E> h = head, p = h, q;;) {
                // If the front node of the node is not empty, update the p node
                if ((q = p.prev) != null &&
                    (q = (p = q).prev) != null)
                    // Check for head updates every other hop.
                    // If p == q, we are sure to follow head instead.
                    p = (h != (h = head)) ? h : q;
                // Node p is out of the queue and starts from scratch
                else if (p.next == p) // PREV_TERMINATOR
                    continue restartFromHead;
                else {
                    // p is first node
                    // Sets the current node as the first
                    NEXT.set(newNode, p); // CAS piggyback
                    // cas updates the related attributes, the pre attributes of the original header node, and the new header node
                    if (PREV.compareAndSet(p, null, newNode)) {
                        // Successful CAS is the linearization point
                        // for e to become an element of this deque,
                        // and for newNode to become "live".
                        if (p != h) // hop two nodes at a time; failure is OK
                            HEAD.weakCompareAndSet(this, h, newNode);
                        return;
                    }
                    // Lost CAS race to another thread; re-read prev
                }
            }
    }

Set the current node as the first node, which is realized by CAS + spin. When it is found that the existing head node is out of the queue, find the head node again

  • linkLast

The link is a node, which is consistent with the head node

    private void linkLast(E e) {
        final Node<E> newNode = newNode(Objects.requireNonNull(e));

        restartFromTail:
        for (;;)
            for (Node<E> t = tail, p = t, q;;) {
                if ((q = p.next) != null &&
                    (q = (p = q).next) != null)
                    // Check for tail updates every other hop.
                    // If p == q, we are sure to follow tail instead.
                    p = (t != (t = tail)) ? t : q;
                else if (p.prev == p) // NEXT_TERMINATOR
                    continue restartFromTail;
                else {
                    // p is last node
                    PREV.set(newNode, p); // CAS piggyback
                    if (NEXT.compareAndSet(p, null, newNode)) {
                        // Successful CAS is the linearization point
                        // for e to become an element of this deque,
                        // and for newNode to become "live".
                        if (p != t) // hop two nodes at a time; failure is OK
                            TAIL.weakCompareAndSet(this, t, newNode);
                        return;
                    }
                    // Lost CAS race to another thread; re-read next
                }
            }
    }

Out of line operation

    public E pollFirst() {
        restart: for (;;) {
            for (Node<E> first = first(), p = first;;) {
                // Queue head node, cas change attribute
                final E item;
                if ((item = p.item) != null) {
                    // recheck for linearizability
                    if (first.prev != null) continue restart;
                    if (ITEM.compareAndSet(p, item, null)) {
                        unlink(p);
                        return item;
                    }
                }
                // Out of line, start again
                if (p == (p = p.next)) continue restart;
                // p is empty, the queue is empty, and null is returned
                if (p == null) {
                    if (first.prev != null) continue restart;
                    return null;
                }
            }
        }
    }

    public E pollLast() {
        restart: for (;;) {
            for (Node<E> last = last(), p = last;;) {
                final E item;
                if ((item = p.item) != null) {
                    // recheck for linearizability
                    if (last.next != null) continue restart;
                    if (ITEM.compareAndSet(p, item, null)) {
                        unlink(p);
                        return item;
                    }
                }
                if (p == (p = p.prev)) continue restart;
                if (p == null) {
                    if (last.next != null) continue restart;
                    return null;
                }
            }
        }
    }

Corresponding to joining the team, pop up the head or tail of the team. The idea is the same

Normal queue operation

Double ended queues can provide queue in and queue out operations like ordinary queues. At this time, they are a FIFO queue, that is, the queue in is added to the tail of the queue, and the queue out obtains elements from the head of the queue

summary

Consistent with the idea of concurrent linked queue, it is implemented by CAS + spin. It only provides the method related to double ended queue

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 by BMN on Wed, 10 Nov 2021 19:18:20 -0800