Analysis of Concurrentlinkedqueue source code in Java concurrency __java

Source: Internet
Author: User
Tags cas class definition volatile

The CAS algorithm is mentioned several times in this paper, and a simple description of CAs (non-blocking algorithm) is first described.

CAS has 3 operands, memory value V, old expected value A, new value B to modify. If and only if the expected value A and memory value v are the same, the memory value V is modified to B, otherwise nothing is done.
Concurrentlinkedqueue is a thread-safe queue. He uses a non-blocking algorithm (CAS) to implement thread-safe. Concurrentlinkedqueue is a line-free secure queue based on link nodes, which uses FIFO rules to sort nodes, and when we add an element, it is added to the tail of the queue; when we get an element, it returns the elements of the queue's head. concurrentlinkedqueue non-variable

In the following source code analysis, we will see that queues are sometimes in an inconsistent state. For this purpose, the Concurrentlinkedqueue uses three invariant (basic invariant, head invariant and tail invariant) to constrain the execution of the method in the queue. The three invariant methods are used to maintain the correctness of the non-blocking algorithm and the basic invariant

The queue must remain unchanged before and after the method is executed:
When the queue is inserted into a new node, there is a (last) node with a next field that is null.
The queue is traversed from head to access all nodes that are NOT NULL for the item field. the invariant and variable type of head

The head must remain unchanged before and after the method is executed:
All "Alive" nodes (which are not deleted) can be traversed from the head by calling the Succ () method.
The head cannot be null.
The next field of the head node cannot be referenced to itself.
The variable of the head before and after the method is executed:
The item field for the head node may or may not be null.
Allow tail lag (lag behind) to head, that is to say: Traversing the queue from head, does not necessarily reach tail. tail and variable of the same type

The tail must remain unchanged before and after the method is executed:
The SUCC () method is called by tail, and the last node is always up to.
Tail cannot be null.
Tail variable before and after the method is executed:
The item field for the tail node may or may not be null.
Allowing tail to lag behind the head, which is to say: Traversing the queue from head, does not necessarily reach tail.
The next field of the tail node can be referenced to itself.
In the following source code analysis, we will analyze the correctness of these three invariant references before and after the Concurrentlinkedqueue and after the call to the queue/team method. concurrentlinkedqueue Structure

public class Concurrentlinkedqueue<e> extends abstractqueue<e>
        implements Queue<e>, Java.io.Serializable

The concurrentlinkedqueue consists of a head node and a tail node, each node being composed of a node element (item) and a reference to the next node (next), which is associated with the node through this next. Thus forming a queue of linked list structures. By default, the head node stores elements that are empty and the tail node equals the head node class implementation and the queue initialization node class definition

The Concurrentlinkedqueue is implemented using a linked list linked to a node. First, let's take a look at the source code for the node class

private static class Node<e> {private volatile E item;    Declared as volatile type private volatile node<e> next;              Declare as volatile node (E item) {//Create New Nodes Lazysetitem (item); 
       Lazy set the value of the item field} E GetItem () {return item; Boolean Casitem (E cmp, E val) {//Set the value of the item field with CAS Directive return Unsafe.compareandswapobject (this, I 
       Temoffset, CMP, Val); 
       } void SetItem (E val) {//Use volatile write to set the value item = Val for the item field; 
       } voidlazysetitem (E val) {//lazy sets the value of the Item field Unsafe.putorderedobject (This, Itemoffset, Val); } void Lazysetnext (node<e> val) {//lazy sets the value of the next field unsafe.putorderedobject (this, NEXTOFFSE 
       T, Val); 
       } node<e> GetNext () {return next; //cas set the value of the next field
       Boolean Casnext (node<e> cmp, node<e> val) {return Unsafe.compareandswapobject (this, nexto 
       Ffset, CMP, Val); 
       private static final Sun.misc.Unsafe unsafe=//Domain update sun.misc.Unsafe.getUnsafe (); 
       Private static final long nextoffset= the offset of the//next domain Objectfieldoffset (UNSAFE, "Next", Node.class);  
 Private static final long itemoffset= the offset of the//item domain Objectfieldoffset (UNSAFE, "item", Node.class); }

In the practical application of concurrentlinkedqueue, a lot of node objects with short life cycle are allocated frequently. To reduce overhead, the item field and next field of the Node class are declared as ordinary volatile types. They use reflection to update node type descriptions through the Atomic Reference domain update (atomicreferencefieldupdater)

Valid node: A node in which the item field is not NULL by traversing the head back through the nodes that can reach it.
Invalid node: a node in which the item field is null by traversing the head backward through the nodes that are reached.
To delete a node: Traverse the unreachable node from head back.
Sentinel node: A node that is linked to itself (the Sentinel node is also to delete nodes).
Header node: The first valid node in the queue, if any.
Tail node: A node in the queue with a null next field (can be an invalid node).
As shown in the following illustration:

The invariant of the head and the invariant of the tail can be seen that the head can only point to valid nodes and invalid nodes, while tail can point to any node, including to delete nodes and Sentinel nodes. In Concurrentlinkedqueue, a new node can only be linked to the rear of the tail node when the team is queued, and only the header node can be removed from the queue when it is out.

In the queue is to add the queue to the end of the queues, in the Concurrentlinkedqueue, insert the new node, regardless of whether the tail node is a valid node, the new node directly into the back of the node. Because tail can point to any node, you must first find the tail node by tail before you can perform an insert operation. If the insertion is unsuccessful (indicating that the other thread has preempted a new node), continue to push backwards. Repeat the above iteration process until the insertion succeeds.
Now let's take a look at the source code

Public Boolean offer (E e) {if (E = = null) throw new NullPointerException (); Before you join the team, create a new node node<e> n = node<e> (E);
        Create a new node//Dead Loop, the team did not succeed repeatedly team. Retry:for (;;)
            {//Create a reference to the tail node node<e> t = tail;
            P is used to represent the tail node of the queue, which is equal to the tail node node<e> p = T by default;
                for (int hops = 0; hops++) {//Find the next node of tail, next node<e> next = SUCC (p); Next node is not NULL, stating that p is not a tail node, need to update p after pointing it to next node if (next!= null) {//If at least two sections have been crossed And the tail is modified (tail is modified to indicate that another thread added a new node to the queue and that the update tail succeeded),//And that the current node is not equal to the tail node if (hops > Hops && T!= tail)//jump out of both internal and external loops to start iterations again (because tail has just been updated by another thread) CO Ntinue retry; B2//Push backwards to the next node p = Next; B3//If p is the tail node, set the NE of P nodesThe XT node is the queue node} else if (P.casnext (null, N)) {//C//If the tail node has greater than or equal to 1 next nodes, the queue node is set to tail
                        node,//update failed It doesn't matter, because the failure indicates that another thread has successfully updated the tail node if (Hops >= hops)//C1 Update tail with the CAS Atomic directive to point to this newly inserted node, allowing failure Castail (t, N); C2 return true; 
            C3} else {//p has a next node that indicates that the next node of P is the tail node, then reset p node p = SUCC (P);//D1} }
        }
    }

From the source code point of view, the whole process of the team to do two things: the first is to locate the tail node; the second is to use the CAS algorithm to set the queue node to the next node of the tail node, and retry the locating tail node if it is unsuccessful

The tail node is not always the tail node, so each team must first find the tail node through the tail node. The tail node may be the tail node, or it may be the next node of the tail node. The first if in the loop body in the code is to determine if the tail has a next node, and then the next node may be the tail node. To get the next node of the tail node to note that the P node equals the next node of P, only one possibility is that the P node and P's next node are equal to NULL, indicating that the queue has just been initialized and is preparing to add the node, so the head node needs to be returned
To get the P node's next node source code is as follows:

/**
     * Returns the successor of P, or the I-node if P.next has been
     * linked to self, which'll only is true I f traversing with a * stale pointer this is now off the
     list.
     *
    final node<e> succ (node<e> p) {
        //Todo:should We skip deleted nodes here?
        node<e> q = p.next;
        return (p = q)? A (): Q;
    }

The following is a description of what might happen to tail, respectively
1, tail point tail node

At the beginning, tail points to the D node, first looking for the successor node of the D node. Because the successor node of D is null, the new node is inserted behind the D node. If the insert succeeds, exit the method, and if the insertion fails (indicating that another thread has just inserted a new node), push backwards to the newly inserted node and start the iteration again. The following illustration is a schematic of the success of the insert:

In the above illustration, tail has not been updated because the number of nodes tail lags behind the tail node has not reached the threshold specified by hops.
2, tail point to non-tail node

At the beginning, tail points to the C node. First find the successor node D of C and then push backwards to node D, followed by the same Code execution path as the "tail pointing tail node" above. The following figure is a schematic diagram of the successful insert:

The tail in the figure above updates the location. Because after the E node is added, the number of tail latency nodes reaches the threshold specified by hops. This triggers the CAS operation to perform the update tail.

private static final int hops = 1;

3, tail lag in head

At the beginning, tail points to A node. First find the successor Node B of a, then push backwards to Node B. Since B is the sentinel node, the jump action is skipped, and the C node skips, starting with the D node head pointing to the backward traversal. The following code execution path is the same as "tail pointing to a non-tail node." The following is a schematic diagram of the structure after the successful insertion of a new node
out of the queue

Out of the queue is to return a node element from the queue and empty the node's reference to the element. The head node is not updated every time the team is out, and when there is an element in the head node, it pops up the elements in the parent node and does not update the parent node. Only when there are no elements in the head node does the outbound operation update the Mind node. This is done by hops variables to reduce the cost of using the CAS update head node, thereby increasing team efficiency.
below I see the source code implementation of the next team:

    Public E-Poll () {node<e> h = head; 
        P represents the head node and requires a team of nodes node<e> p = h; 
            for (int hops = 0; hops++) {//Get element E item = P.getitem () of P node; If the element of P node is not empty, the element referenced using the CAS setting p node is null if (item!= NULL && p.casitem (item, NULL)) {//IF 
                    The iterative process has passed no less than 1 nodes, i.e. if (hops >= hops) {///the next node of P node is set to head node 
                    node<e> q = P.getnext ();  If q is not NULL, the set head points to the successor node Q, otherwise the setting head points to the current node P (when the queue is empty, only one pseudo node P) updatehead (H, (q!= null)? Q:              
                p);                                        
            //Returns the value of the item field that is removed from the node return item; //If the element of the head node is empty or the header node has changed, this indicates that the header node has been modified by another//thread. 
            Then get the next node of the p node node<e> next = SUCC (p); If the next node of P is also empty, it means that the queue is empty if (next = = null) {
                The set head points to the P node (at which time the queue is empty and only one pseudo node P) updatehead (H, p);                                                
            Exit the loop break;                                                
        //If the next element is not empty, set the next node of the head node to the head node P = Next; 
    return null;
 }

The

First gets the elements of the header node. It then determines if the header node element is empty, and if it is empty, it means that another thread has made an outbound operation. Takes the element of that node away, or, if not NULL, sets the reference of the header node to null using CAS, and if the CAS succeeds, The element of the head node is returned directly, and if it is unsuccessful, it means that another thread has already made a team out of it. The head node has been updated, causing the element to change and the header node needs to be retrieved. Header node Positioning

Depending on the head's invariant and variable, there are two possible head positions in the queue before performing a team operation:
Head points to a valid node. The
head points to an invalid node.
1, when the head points to a valid node

Out of the team, first gets the value of the item field of the a node to which the head is pointing, and then the value of the item field for the a node in the CAS setting is null. If successful, the value of the item field of A node is returned directly because the number of nodes crossed at this time is 0. If unsuccessful, other threads have preempted the node, pushing backwards to node B. Repeat this process until a node is successfully deleted, or null if the queue has not been deleted successfully after the traversal. The following is a successful deletion of the structure diagram:

In the above illustration, although A node is set to an invalid node, the head still points to it because the number of nodes that the delete operation has passed has not reached the threshold specified by hops.
Next, let's look at the schematic diagram of the second scenario
2, head pointing to invalid node

First gets the value of the head pointing to the item field of the node and, because of NULL, pushes backwards to node B. When the value of the item field for the B node is obtained, this value is set to null through CAS. If successful, the executive head update is triggered because the threshold specified by hops has been reached. If it is unsuccessful (indicating that the other thread has preempted the B node), continue to push backwards to the C node. Repeat this process until a valid node is deleted. Returns null if the queue has not been deleted successfully after traversing. The following figure is a successful deletion of the structure diagram:

from the above figure, we can see that, during the delete operation, the head over the number of nodes reached the threshold, triggering the executive head of the update, so that it points to the C node.
After removing the header node from above, the two schematic diagram shows that the queue after performing the team operation still satisfies the three invariant type. Summary

The implementation of the

Concurrentlinkedqueue non-blocking algorithm is very sophisticated and complex. It uses CAS atomic directives to handle concurrent access to data. At the same time, it allows queues to be in an inconsistent state. This feature separates the two steps contained in the team/outbound operation that require an atomic execution, thereby effectively narrowing the range of the atomicity (updated value) in the team/outbound as a unique variable. Because the queue may be in an inconsistent state, this concurrentlinkedqueue uses three invariant to maintain the correctness of the non-blocking algorithm.
Reference books:
Java concurrency Programming, JDK source code

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.