The implementation of blocking queue has been explained before. Today, we will continue to understand the implementation of non blocking queue in the source code. Next, let's see how the non blocking queue of concurrent linked queue completes the operation
Preface
JDK version No.: 1.8.0«
ConcurrentLinkedQueue is an unbounded thread safe FIFO non blocking queue based on linked list. The biggest difference lies in the non blocking feature. The blocking queues explained before will block in various ways. In the ConcurrentLinkedQueue, the non blocking operations will be completed through CAS operations. The update of head and tail is similar to the slack mechanism explained in LinkedTransferQueue. Only when the slack threshold is greater than or equal to 2, can they be updated to minimize the number of CAS operations. Of course, such operations also improve the complexity of code implementation
Class definition
From the diagram, we can see that ConcurrentLinkedQueue does not implement the BlockingQueue interface
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable
Implementation process
In order to understand the operation of its internal implementation, you can see the following process chart to understand the transformation process of its internal nodes entering and leaving the queue
Constant / variable
In addition to the constants that CAS needs to use, there are only two reference nodes: head and tail. In the comment part, you can see the author's description. Here's the explanation:
head node:
- All undeleted nodes can be accessed by the head node by executing the succ() method
- head node is not null
- The head node next cannot point to itself
- The item of the head node may or may not be null
- The tail node can lag behind the head node. At this time, the tail node cannot be accessed from the head node
Tail node (tail's next is null):
- The last node of the queue can be accessed by executing the succ() method through the tail node
- tail node is not null
- item of tail node may or may not be null
- The tail node can lag behind the head node. At this time, the tail node cannot be accessed from the head node
- The tail node next may or may not point to itself
Because the head node and tail node are not updated in real time, they can only be updated when the relaxation threshold is reached, which may lead to the phenomenon that the head node is behind the tail node
/** * A node from which the first live (non-deleted) node (if any) * can be reached in O(1) time. * Invariants: * - all live nodes are reachable from head via succ() * - head != null * - (tmp = head).next != tmp || tmp != head * Non-invariants: * - head.item may or may not be null. * - it is permitted for tail to lag behind head, that is, for tail * to not be reachable from head! */ private transient volatile Node<E> head; /** * A node from which the last node on list (that is, the unique * node with node.next == null) can be reached in O(1) time. * Invariants: * - the last node is always reachable from tail via succ() * - tail != null * Non-invariants: * - tail.item may or may not be null. * - it is permitted for tail to lag behind head, that is, for tail * to not be reachable from head! * - tail.next may or may not be self-pointing to tail. */ private transient volatile Node<E> tail; // CAS operation private static final sun.misc.Unsafe UNSAFE; private static final long headOffset; private static final long tailOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = ConcurrentLinkedQueue.class; headOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("head")); tailOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("tail")); } catch (Exception e) { throw new Error(e); } }
Inner class
Node implementation is relatively simple, and there is no complex part. It mainly updates variables through CAS operation
private static class Node<E> { volatile E item; volatile Node<E> next; /** * Constructs a new node. Uses relaxed write because item can * only be seen after publication via casNext. */ Node(E item) { UNSAFE.putObject(this, itemOffset, item); } boolean casItem(E cmp, E val) { return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } void lazySetNext(Node<E> val) { UNSAFE.putOrderedObject(this, nextOffset, val); } 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 { 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); } } }
Construction method
The nonparametric construction method creates a null node, and the head and tail nodes point to this null node. It is relatively simple to add nodes in a loop when constructing set parameters. It mainly needs to understand the changes when creating the default nonparametric constructor
public ConcurrentLinkedQueue() { head = tail = new Node<E>(null); } public ConcurrentLinkedQueue(Collection<? extends E> c) { Node<E> h = null, t = null; for (E e : c) { checkNotNull(e); Node<E> newNode = new Node<E>(e); if (h == null) h = t = newNode; else { t.lazySetNext(newNode); t = newNode; } } if (h == null) h = t = new Node<E>(null); head = h; tail = t; }
Important method
updateHead
Under the premise of H! = p, try to update the head point to p. if it succeeds, try to update the original head node to point to itself, indicating that the node leaves the queue
/** * Tries to CAS head to p. If successful, repoint old head to itself * as sentinel for succ(), below. */ final void updateHead(Node<E> h, Node<E> p) { if (h != p && casHead(h, p)) h.lazySetNext(h); }
succ
Obtain the successor node of the p node. When next points to itself, the node itself may be in the status of leaving the queue. At this time, the head node is returned
/** * Returns the successor of p, or the head node if p.next has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. */ final Node<E> succ(Node<E> p) { Node<E> next = p.next; return (p == next) ? head : next; }
offer
The core method of team entry operation, team entry must be successful, and the return value is true, which means that the team entry will try to operate until it is successful. There are three main situations in the circular attempt:
- Find the last node and try to update the next node to the new node. If it fails, it means that the next is updated by other threads. At this time, judge again. If it succeeds, judge whether the tail node has lagged more than one node. If it is, try to update the tail
- The last node found before has left the queue (p = p.next). If the tail has been updated by other threads, it will be updated to the tail. Otherwise, the last node will be found from the head node (because the tail can lag behind the head)
- This node is not the last node and does not leave the queue at the same time. If the tail has been updated by other threads, it will be updated to the tail. Otherwise, the loop will continue from the next of the current node
public boolean offer(E e) { // Sentence blank checkNotNull(e); final Node<E> newNode = new Node<E>(e); // Loop until successful for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; // p this is the last node if (q == null) { // Start trying to update the next point of p to the newly inserted node // If the next of p is not updated successfully, it means that the next has been updated by other nodes first, and the judgment attempt is repeated if (p.casNext(null, newNode)) { // Only when more than one node has been added after the tail points to a node can the tail node point be updated // That is to say, update is only attempted when slack > = 2 if (p != t) // hop two nodes at a time // Failure may be updated by another thread casTail(t, newNode); // Failure is OK. return true; } // Lost CAS race to another thread; re-read next } // P is not the last node. At the same time, p = p.next indicates that P has left the team and needs to update P else if (p == q) // If the tail node has been updated, update the tail. Otherwise, search the last node from the head node p = (t != (t = tail)) ? t : head; else // P is not the last node. At the same time, P is not removed from the queue. If the tail is updated by other threads, P is updated to a new tail. Otherwise, P is updated to p.next to continue the cycle p = (p != t && t != (t = tail)) ? t : q; } }
poll
The core method of out of line operation is to try until it is successful. There are four main situations in the cyclic attempt:
- Find the head node. If the item is not null, try to update it to null to indicate that the node has been queued. If it is successful, judge whether the head node needs to be updated to return the item
- If the item is null or has been obtained by another thread and the current node is the last node, try to update the header head to point to the current node and return null
- The current node is not the last node. If it has left the team, it will be cycled from the head again
- If the current node does not leave the queue, it will be updated to the next node for cycle judgment
public E poll() { restartFromHead: // Loop until successful for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; // If the item is not null, try to update it to null (indicating that the node has been queued) if (item != null && p.casItem(item, null)) { // The outgoing node is not the previous head node. If the distance between the old head node h and the actual head node is greater than 1, the head will be updated if (p != h) // hop two nodes at a time // If there is no node after the outbound node, try to update the head node to the outbound node itself (item = null). If there is a node, try to update it to the node after the outbound node updateHead(h, ((q = p.next) != null) ? q : p); return item; } // item is empty or has been obtained by another thread // If the p node itself is the last node, try to update the head and update the original h node to point to itself, and return null else if ((q = p.next) == null) { updateHead(h, p); return null; } // P is not the last node. If p == p.next, it means that P node has left the team. Then jump to restartFromHead and re cycle the judgment from the beginning else if (p == q) continue restartFromHead; // If P is not the last node and P does not leave the queue, update p to point to the next node of P, and make a circular judgment else p = q; } } }
peek
Similar to the poll method, it does not queue the nodes, but only gets the item value of the header node. Of course, the middle may also help update the head direction
public E peek() { restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; if (item != null || (q = p.next) == null) { updateHead(h, p); return item; } else if (p == q) continue restartFromHead; else p = q; } } }
first
Similar to the poll method, poll returns an item, and here it returns a node. If it is a null node (item == null), it returns null directly
Node<E> first() { restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { boolean hasItem = (p.item != null); if (hasItem || (q = p.next) == null) { updateHead(h, p); return hasItem ? p : null; } else if (p == q) continue restartFromHead; else p = q; } } }
remove
Delete the element from the queue. Judge whether the node has left the queue by whether the item is null. If yes, continue to judge. If the casItem(item, null) succeeds, it means that the node has been removed successfully. If it fails, it means that it has been dequeued by other threads, then continue to judge
public boolean remove(Object o) { if (o != null) { Node<E> next, pred = null; for (Node<E> p = first(); p != null; pred = p, p = next) { boolean removed = false; E item = p.item; // item judgment (non departure node), if not satisfied, continue to judge the successor node if (item != null) { if (!o.equals(item)) { next = succ(p); continue; } // If a matching node is found, an attempt is made to update the item to null, indicating that the current node has left the team removed = p.casItem(item, null); } // Subsequent nodes. To this point, the matching nodes have been deleted (deleted by other threads or the current thread) next = succ(p); // Disassociate matching nodes if (pred != null && next != null) // unlink pred.casNext(p, next); // If it is a node deleted by the current thread, it will return. Otherwise, it will continue to judge and continue to return until it matches or there is no matching node if (removed) return true; } } return false; }
addAll
Adding the elements in set c to the queue and to the end of the original queue is similar to the above offer method
public boolean addAll(Collection<? extends E> c) { if (c == this) // As historically specified in AbstractQueue#addAll throw new IllegalArgumentException(); // Define two pointer nodes pointing to the head and tail of set c // First, transform c into Node list Node<E> beginningOfTheEnd = null, last = null; for (E e : c) { checkNotNull(e); Node<E> newNode = new Node<E>(e); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { last.lazySetNext(newNode); last = newNode; } } if (beginningOfTheEnd == null) return false; for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; // p is the last node of the queue if (q == null) { // Connect the queue with the newly created linked list above, update failure recycling continues if (p.casNext(null, beginningOfTheEnd)) { // tail update failed retry if (!casTail(t, last)) { t = tail; if (last.next == null) casTail(t, last); } return true; } } // p is not the last node and has left the team else if (p == q) // If the tail node has been updated, it will be updated to tail. Otherwise, the last node will be searched from the head node p = (t != (t = tail)) ? t : head; else // P is not the last node. At the same time, P is not removed from the queue. If the tail is updated by other threads, P is updated to a new tail. Otherwise, P is updated to p.next to continue the cycle p = (p != t && t != (t = tail)) ? t : q; } }
iterator
The iterator is similar to the iterator explained in the queue before. The source code is not very complex. In the remove method, the item is set to null, and the correlation between the front and back nodes will not operate, so as to prevent problems in multi-threaded traversal
In the construction method, the advance() method is executed, and the node nextNode and its item reference for the next execution are set in advance. hasNext can judge nextNode to ensure the weak consistency of iterators. Once hasNext returns true, the corresponding item will be obtained by calling next, even if the node item has been set to null
public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { /** * next Returned Node */ private Node<E> nextNode; /** * Save the next item to prevent the node from being deleted and calling next to get the value when hasNext is true */ private E nextItem; /** * If the node returned from the latest call to next is deleted by calling remove, it will be reset to null to avoid deleting the element that should not be deleted */ private Node<E> lastRet; /** * The Node returned from the first call to next is saved during construction */ Itr() { advance(); } /** * Moves to next valid node and returns item to return for * next(), or null if no such. */ private E advance() { lastRet = nextNode; E x = nextItem; Node<E> pred, p; if (nextNode == null) { p = first(); pred = null; } else { pred = nextNode; p = succ(nextNode); } for (;;) { if (p == null) { nextNode = null; nextItem = null; return x; } E item = p.item; if (item != null) { nextNode = p; nextItem = item; return x; } else { // Skip null node Node<E> next = succ(p); if (pred != null && next != null) pred.casNext(p, next); p = next; } } } public boolean hasNext() { return nextNode != null; } public E next() { if (nextNode == null) throw new NoSuchElementException(); return advance(); } public void remove() { Node<E> l = lastRet; if (l == null) throw new IllegalStateException(); // rely on a future traversal to relink. l.item = null; lastRet = null; } }
summary
ConcurrentLinkedQueue is an unbounded thread safe FIFO non blocking queue based on linked list. The main part of the overall source code is two points:
- No lock operation, no block operation, update variables with CAS
- The head and tail nodes are not updated in real time. Only when slack > = 2 can they be updated
It's easy to clarify its implementation and operation process with the diagram, which is much simpler than the previous LinkedTransferQueue source code
If there is any problem in the above content, please point out that the author will correct it in time after verification. Thank you