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