title: Deep analysis ConcurrentLinkedQueue principle
date: 2019-04-23 20:19:04
categories:
- Java Concurrency
tags: - Concurrent Container
Introduction
There are many thread-safe concurrent containers in the JUC package that can be used to achieve thread safety without having to manually set locks.Among these concurrent containers, there are blocking and non-blocking (simply, threads can be blocked when they place and remove elements).ConcurrentLinkedQueue is implemented in a non-blocking manner. This concurrent container class implements a thread-safe queue by cycling CAS operations. It does not cause the current thread to be paused and therefore avoids the overhead of thread context switching.
Blocking containers are thread-safe through locks, whereas non-blocking is achieved through CAS.
Principle of CAS
CAS(Compare and Swap): Compare and exchange.It is the name for a processor instruction that CAS implements in Java by calling JNI code.The CAS operation, as its name implies, compares before exchanging (or updating), as seen by a pseudocode:
for(;;){ ... public boolean cas(V a, V b ,V c){ //A is the variable you want to modify, b is the value of a when the current thread calls the CAS operation (that is, the old value of a), and c is the new value if(b == a){ //Check if other threads have modified a b = c; //To update return true; //Update Successful } return false; //update operation } ... }
When a thread is performing a CAS operation, it can modify it if the value it wants to modify is the same as the old value provided by the thread calling the CAS operation, indicating that other threads have not modified it.The other threads fail to update and continue trying until they succeed.
ConcurrentLinkedQueue principle
Simply put, ConcurrentLinkedQueue is equivalent to the thread-safe version of LinkedList, which is a one-way chain list with a private static class Node <E> inside it, which encapsulates elements and saves the next node (next), where Node <E> performs CAS operations through a UNSAFE class inside.
The main code for the Node class:
private static class Node<E> { volatile E item; // volatile declaration volatile Node<E> next; // volatile declaration // Construction method at address itemOffset, value replaced by item Node(E item) { UNSAFE.putObject(this, itemOffset, item); } // CAS operation: Compare and exchange atomic variables that change the itemOffset address.If the value of the variable is cmp and is successfully replaced with valReturns true boolean casItem(E cmp, E val) { return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } // Replace the value of the nextOffset address with x void lazySetNext(Node<E> val) { UNSAFE.putOrderedObject(this, nextOffset, val); } // CAS operation: Compare and exchange atomic variables that change the nextOffset address.If the value of the variable is cmp and is successfully replaced with valReturns true boolean casNext(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long itemOffset; private static final long nextOffset; /** * Static code block, get itemOffset and nextOffset */ static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class k = Node.class; itemOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("item")); nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } }
CAS operations for tail and head nodes:
// CAS operation: Compare and exchange atomic variables that change tailOffset address.If the value of the variable is cmp and is successfully replaced with valReturns true private boolean casTail(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val); } // CAS operation: Compare and exchange atomic variables that change the headOffset address.If the value of the variable is cmp and is successfully replaced with valReturns true private boolean casHead(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); }
UNSAFE class methods used:
// Gets the offset address of f in heap memory public native long objectFieldOffset(Field f); // Gets the offset address of static f in heap memory public native long staticFieldOffset(Field f); // Atomic variable that changes offset address.If the value of the variable is expected and is successfully replaced with x to return the true CAS operation public final native boolean compareAndSwapObject(Object o, long offset, Object expected,Object x); // Replace the value of the offset address with x and notify other threads.Because there is a Volatile, similar to putObject public native void putObjectVolatile(Object o, long offset, Object x); // Get a value with offset address public native Object getObjectVolatile(Object o, long offset)
Two variables tail and head are maintained in ConcurrentLinkedQueue, and an empty node is created and assigned to tail and head when the ConcurrentLinkedQueue instance is created.
private transient volatile Node<E> head; private transient volatile Node<E> tail; public ConcurrentLinkedQueue() { head = tail = new Node<E>(null); }
Queue (add)
Queuing means adding the newly created ode Node to the end of the queue, as follows ( Pictures from www.ifeve.com).

- First, add the element 1 node after the head node, that is, the next node of the head node is element 1, because the tail node and the head node are the same node when the ConcurrentLinkedQueue is initialized.
- In the second step, set the element 2 node as the next node of the element 1 node and the tail node as the element 2 node.
- ~~
public boolean offer(E e) { checkNotNull(e); //Create a new queued node final Node<E> newNode = new Node<E>(e); //Create a reference t pointing to the tail node, which is equivalent to an intermediate variable used to compare with p //p means tail node //Dead loop, keep trying CAS operation, value until success, return true for (Node<E> t = tail, p = t;;) { //Get the next node of the p node Node<E> q = p.next; //If q is empty, p is the tail node if (q == null) { //p is the tail node, after adding a new node to the p node, CAS operation: compare and exchange, if the value of nextOffset equals null, indicating that other threads have not modified it, then the CAS operation succeeds and jumps out of the loop if (p.casNext(null, newNode)) { //If p is not equal to t, tail is not the tail node, then the new node is set as the tail node by the CAS operation, and if it fails, another thread has modified it if (p != t) //If tailOffset has a value of t, try setting the new node to the tail node casTail(t, newNode); return true; } } //If p equals q, p and Q are both empty, that is, ConcurrentLinkedQueue has just been initialized else if (p == q) p = (t != (t = tail)) ? t : head; //p has a next node, which means that the next node of p is the tail node, then you need to update p and point it at the next node else p = (p != t && t != (t = tail)) ? t : q; } }
-
Get the tail node first.
-
The next node of the tail node is then determined to be empty.
-
If empty, try adding nodes using CAS operations
- Returns true if successful.
- If it fails, it means that another thread has added a new node, then the tail node needs to be retrieved.
-
If it is not empty, it indicates that a new node has been added by another thread.
- Just initialized.
- Update the tail node.
-
Through analysis, it is known that each time a queue is enrolled, the tail node will be located first. After successful positioning, the queue will be enrolled by p.casNext(null, newNode) CAS operation, because P and T are not the same every time, that is, tail node will not be reset every time a queue is enrolled, which reduces the number of casTail(t, newNode) CAS operations to set the tail node, reduces overhead, and improves entryTeam efficiency.
Out of Queue (poll)
Out of the queue is to eject the head node from the queue, empty its reference, and return the elements in the node.

As with queuing, the head node is not updated every time an out-of-queue operation occurs. Updating the head node only occurs when the element of the head node is empty.
public E poll() { //Dead loop, queue out operation, until successful, return, either an item or null. restartFromHead: for (;;) { //Create a reference h pointing to the header node, which is equivalent to an intermediate variable used to compare with p //p represents the header node for (Node<E> h = head, p = h, q;;) { //item is the element of the header node E item = p.item; //If the p-node element is not empty, the CAS operation is used: If the value of the item address of the current thread equals the value of the itemOffset address, set the element value of the p-node to empty and return the item if (item != null && p.casItem(item, null)) { //If p is not equal to h, that is, the head is not the head node, set the next node of p as the head node if (p != h) updateHead(h, ((q = p.next) != null) ? q : p); return item; } //Indicates that it is the last node and jumps out of the loop else if ((q = p.next) == null) { updateHead(h, p); return null; } //p is the same as q, Q and p are empty, skip to outer loop, start over else if (p == q) continue restartFromHead; //P has a next node, set p to point to its next node else p = q; } } }
- First get the element of the header node.
- Then determine if the header node element is empty.
- If empty, it means another thread has queued to remove elements from the node.
- If not null, use CAS to set the reference of the header node to null.
- If CAS succeeds, the elements of the header node are returned directly.
- If unsuccessful, it means that another thread has updated the head node with a queue operation, causing the element to change and requiring the head node to be retrieved.