ConcurrentLinkedQueue source code analysis

In concurrent programming, we may often need to use thread-safe queue, JDK provides a queue two modes: blocking and non-blocking queue queue. Use locks for blocking queue, non-blocking queue using CAS implementation. ConcurrentLinkedQueue is a list unbounded thread-safe queue implementation, based on respect. Let's look at how to use JDK is a non-blocking way to achieve thread-safe queue of ConcurrentLinkedQueue.

Member property

ConcurrentLinkedQueue the head and tail nodes, connected between the node and the next node through, thereby the composition of a linked list queue structure.

private transient volatile Node<E> head;
private transient volatile Node<E> tail;
复制代码

Node Deliverable

Node has two properties under the item and point to a node of the next, and the next item are declared volatile type, use the CAS to ensure the thread safety update.

private static class Node<E> {
    volatile E item;
    volatile Node<E> next;

    Node(E item) {
        UNSAFE.putObject(this, itemOffset, item);
    }
	//更改Node中的数据域item	
    boolean casItem(E cmp, E val) {
        return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }
    //更改Node中的指针域next
    void lazySetNext(Node<E> val) {
        UNSAFE.putOrderedObject(this, nextOffset, val);
    }
    //更改Node中的指针域next
    boolean casNext(Node<E> cmp, Node<E> val) {
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    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 default constructor with no arguments, head, and tail defaults to the case where the item is null sentinel node Node. When the element is added to the tail enqueue, dequeue time acquired from a head of the queue element.

public ConcurrentLinkedQueue() {
    head = tail = new Node<E>(null);
}
复制代码

offer methods

Before reading the source code and its implementation in accordance with process analysis, give conclusion: tail不一定指向对象真正的尾节点behind after analysis we will find this feature.

private static void checkNotNull(Object v) {
    if (v == null)
        throw new NullPointerException();
}
public boolean offer(E e) {
    //(1)如果e为null会抛出空指针异常
    checkNotNull(e);
    //(2)创建一个新的Node结点,Node的构造函数中会调用Unsafe类的putObject方法
    final Node<E> newNode = new Node<E>(e);
    //(3)从尾节点插入新的结点
    for (Node<E> t = tail, p = t;;) {
        //q为尾节点的next结点,但是在多线程中,如果有别的线程修改了tail结点那么在本线程中可以看到p!=null(后
        //面的CAS就是这样做的)
        Node<E> q = p.next;
        //(4)如果q为null,说明现在p是尾节点,那么可以执行添加
        if (q == null) {
            //(5)这里使用cas设置p结点的next结点为newNode
            //(传入null,比较p的next是否为null,为null则将next设置为newNode)
            if (p.casNext(null, newNode)) {
                //(6)下面是更新tail结点的代码
                //在CAS执行成功之后,p(原链表的tail)结点的next已经是newNode,这里就设置tail结点为newNode
                if (p != t) // hop two nodes at a time
                    // 如果p不等于t,说明有其它线程先一步更新tail
                    // 也就不会走到q==null这个分支了
                    // p取到的可能是t后面的值
                    // 把tail原子更新为新节点
                    casTail(t, newNode);  // Failure is OK.
                return true;
            }
        }
        //如果被移除了
        else if (p == q)
            //(7)多线程操作的时候,可能会有别的线程使用poll方法移除元素后可能会把head的next变成head,所以这里需要找到新的head:这里请参考后面的poll方法的讲解图示进行理解
            p = (t != (t = tail)) ? t : head;
        else
            // (8)查询尾节点
            p = (p != t && t != (t = tail)) ? t : q;
    }
}
复制代码

The above method is to achieve and offer comments, here we are divided into single-threaded and multi-threaded execution execute both cases, the realization of the entire process step by step analysis in accordance with the above code. First discuss the process of single-threaded execution

Single-threaded execution

Performed at a single-threaded environment, then implemented method according to a determination step performed directly, by following the appropriate process diagram to illustrate the

  1. First, when a thread calls the offer method, a non-null check code (1), the null Throws an exception, does not perform as null(2)

  2. Codes (2) Node<E> newNode = new Node<E>(e)used as a parameter item constructor, creates a new node

  3. The code (3) for (Node<E> t = tail, p = t;;)starts from the end of the queue spin cycle, a new node is added to ensure that the tail of the queue

  4. Obtained tailin the nextnode ( q), in this case shown in FIG queues as follows ( 默认构造方法中将head和tail都指向的是一个item为null的结点). At this time qpoint is null

  1. (4) Code if (q == null)execution is determined at q==nullis true

  2. Code (5) if (p.casNext(null, newNode))execution is at pthe next junction to update the CAS created for us newNode. (Where p is next CAS will determine whether null, null is only updated newNode)

  3. At this time p==t, it will not perform the update of the block of code tail (6) casTail(t, newNode), but the method exits from the offer. This is the case when the queue is shown below

  1. So this thread is executed, but the tail has not changed too: in fact when to offer a second time, you will find p=tail,p.next!=null, will execute code (8) p = (p != t && t != (t = tail)) ? t : q, a simple analysis:

    • p != t: P to tail, t is the tail, so tofalse
    • t != (t = tail): Obviously it is false
  2. So the result is that p = q, then for the next cycle, after the determination p.nextis null, CAS can be successful, because p!=t, it will update the tail node.

So given the above conclusions here reflects that tail并不总是指向队列的尾节点, in fact, it can also be another way to make tail pointing to the end node, that is realized as follows

if (e == null)
    throw new NullPointerException();
Node<E> n = new Node<E>(e);
for (;;) {
    Node<E> t = tail;
    if (t.casNext(null, n) && casTail(t, n)) {
        return true;
    }
}
复制代码

But if a large number of enqueue operation, then each will need to update the CAS node pointed tail, when a large amount of data when the impact on performance is great. Therefore, ultimately, the CAS operation is reduced to improve the performance of a large number of the enqueue operation: intervals (between the tail end node points and true differential) update operation is performed 1 time CAS tail by a tail node (but the distance long the negative effect that each time the team into the longer end node positioning time, because the loop body needs 多循环一次来定位出尾节点(将指向真正的尾节点,然后添加newNode)). In fact, in the previous analysis of member properties, we also know, tail is modified volatile, but essentially CAS mode or for read and write operations volatile variables, and volatile write operation is greater than the cost of a read operation, so Concurrent Linked Queue的是线上是通过增加对于volatile变量的读操作次数从而相对的减少对其写操作. The following is a single thread of execution time of the tail pointing method offer a schematic diagram of a change

Multi-threaded execution

Single thread execution demonstrated above, then what happens when executed in a multithreaded environment, it is assumed here that two concurrent threads of execution.

Case 1

这里分析的其实就是假设多个线程都会执行到CAS更新p.next结点的代码We look at the following, assuming threadA call offer (item1), threadB call offer (item2) to perform p.casNext(null, newNode)at the position

  • CAS atomic operation, it is assumed that the implementation of the above threadA first line of code, and the updated successfullyp.next为newNode
  • This time threadB naturally when making comparisons CAS will fail ( p.next!=null), so the cycle re-acquire tail node will be next and then try to update

Queue situation at this time is as follows

  • After obtaining threadB tail node, found q!=null( q=p.next,p=tail)

  • Continues to judge p==qalso false, the execution code (8)

  • Analyze p = (p != t && t != (t = tail)) ? t : qthis Code

    1. p != t: P to tail, t is the tail, so tofalse
    2. t != (t = tail): Obviously it is false
    3. Therefore, the above result is three head operation p=q, the results shown below

  • Then perform the cycle again, this time p.nextis null, so you can execute code (5) p.casNext(null,newNode). CAS obtained this time is determined p.next == null, it may be providedp.next=Node(item2)

  • After the success of CAS, is determined p!=t(shown above), it can be set as a tail Node (item2) a. Then exit from the offer, this time for the queue situation

As can be seen, 情况1it is assumed that the initial two threads at all times to get that p=tail,p.next=null, then the implementation of CAS will try to add newNode, but only one thread can successfully added the first time cycle and then return to true ( 但是这时候的tail还没有变化,类似单线程总结那块的tail和真正的尾节点差1或0), so another thread try again in the second cycle, the time will change point, namely p of p = (p != t && t != (t = tail)) ? t : qthe code at. Then in the third cycle can really add CAS success (of course, here we are hypothetical analysis of the two threads, the actual multi-threaded environment is certainly more complex, but the logic is similar)

Case 2

Analysis of the code here is primarily p = (p != t && t != (t = tail)) ? t : qanother case, that is p=tthe case, it would first analyze this line, assuming now

  • p != tIt is true,
  • t = (t = tail): ! is also true (t is recycled to the left point to the beginning of the tail when the information obtained in parentheses to regain tail and assigned to t, this time there is another thread may have changed the volatilemodified the tail of)

Then the result is p redirected queue tail end node, the following hypothetical case one such

In fact this is the use volatile的可见性, 快速将一个要添加元素的线程找到当前队列的尾节点to avoid FIG later, read this time is assumed threadA variable tail, just add several Node ThreadB at this time, then modifies the tail pointer, then the time t = thread A is executed again when the tail T points to another node, so threadA t twice before and after the read variable node pointed to is not the same, i.e. t != (t = tail)is true, and because changes to a node point t p != tis also true, the result of executing the line at this time is t p and t newest pointer to the same node , and at this time t is the real queue tail node. Well, now navigate to the tail of the queue real node, you can offer to perform the operation.

Case 3

The above operations are multithreaded to add elements of our discussion, then when both threads offer also thread when it calls the poll method, where a block of code should call (7) offer a method. Speaking poll because no method, so the code here on the first no explanation, say the following poll method executes multiple threads of time, will take the offer-poll-offer such a case is described, then the method may offer execution this is a few lines of code.

else if (p == q)
    //(7)多线程操作的时候,可能会有别的线程使用poll方法移除元素后可能会把head的next变成head,所以这里需要找到新的head
    p = (t != (t = tail)) ? t : head;
复制代码

add method

public boolean add(E e) {
    return offer(e);//这里还是调用的offer方法,上面说到了,这里就不说明了
}
复制代码

poll method

poll method is to obtain the head of the queue and removes an element if the queue is empty it returns null, look at the source poll following method, and then analyzed or were performed under single and multithreaded

public E poll() {
    //标记
    restartFromHead:
    for (;;) {//自旋循环
        for (Node<E> h = head, p = h, q;;) {
            //(1)保存当前结点的item
            E item = p.item;
            //(2)如果当前结点的值不为null,那就将其变为null
            if (item != null && p.casItem(item, null)) {
                //(3)CAS成功之后会标记当前结点,并从链表中移除
                if (p != h) // hop two nodes at a time
                    updateHead(h, ((q = p.next) != null) ? q : p);
                return item;
            }
            //(4)如果队列为空会返回null
            else if ((q = p.next) == null) {
                updateHead(h, p);
                return null;
            }
            //(5)如果当前结点被自引用了,重新找寻新的队列头节点
            else if (p == q)
                continue restartFromHead;
            else
                p = q; //进行下一次循环,改变p的指向位置
        }
    }
}
final void updateHead(Node<E> h, Node<E> p) {
    if (h != p && casHead(h, p))
        h.lazySetNext(h);
}
复制代码

Above we have looked at the source code of the poll method, here we follow to implement this method to understand it by way of illustration.

Single-threaded execution

poll operation is to obtain elements from the first team, so:

  • Cycle starts from the head node first for (Node<E> h = head, p = h, q;;)obtains the current head node queue, of course, if the queue start is empty, as shown on

Since the head node is the presence of a sentinel node, it will execute the code (4) else if ((q = p.next) == null), because the queue is empty, so the direct execution updateHead(h, p), while updateHeadthe method of determination h=p, the direct return null.

  • The above is the case where the queue is empty, then when the queue is not empty when it is assumed that the queue is now shown as follows

  • Therefore, the code (4) else if ((q = p.next) == null)the determination at that false,

  • Therefore, executing the next determination else if (p == q), the determination result is false

  • Finally executed p=q, after the expiry of the next cycle of the queue status

  • In the new cycle, the determination may be obtained item! = Null, so use CAS manner item null set, (this is a test case in a single-threaded) so continues if(p!=h), the determination result is true. So if the content execution: updateHead(h, ((q = p.next) != null) ? q : p)What does it mean? As shown, the result here is q = null, so that the incoming parameters (positions as shown in FIG pointed to by p) p

    //updateHead方法的参数(Node h,Node p)
    q = p.next;
    if(null != q) {
    	//第二个参数就是q
    } else {
        //第二个参数就是p
    }
    复制代码

    Then perform updateHead method, where we need to look at the details of the method

    final void updateHead(Node<E> h, Node<E> p) {
        //如果h!=p,就以CAS的方式将head结点设置为p
        if (h != p && casHead(h, p))
            //这里是将h结点的next结点设置为自己(h)
            h.lazySetNext(h);
    }
    //Node类中的方法
    void lazySetNext(Node<E> val) {
        UNSAFE.putOrderedObject(this, nextOffset, val);
    }
    复制代码

    After performing these then, the queue state is what it, as shown in FIG. Finished just return the element removed anger item1

Multi-threaded execution offer, poll

Analysis of the single-threaded, calls the poll method of execution flow above. In fact, just then offer method, there was a pit is not resolved. Described as follows

  • Suppose there is a queue element original item1

  • Suppose at the time of call offer thread1 method, another thread just calls the poll method head node is removed, according to the above analysis, the situation after poll following method call queue

  • (Recall here offer the execution flow) so when thread1 continue execution, executed for (Node<E> t = tail, p = t;;)after obtaining the position of the tail points as shown above, the tail pointer points to the next node, or directed locations themselves. Therefore, Node<E> q = p.nextafter performing q = tail = p. Therefore, it is determined in the following will offer the method

    else if (p == q)
        //(7)多线程操作的时候,可能会有别的线程使用poll方法移除元素后可能会把head的next变成head,所以这里需要找到新的head
        p = (t != (t = tail)) ? t : head;
    复制代码

    Or a simple analysis of p = (t != (t = tail)) ? t : headthe sentence, as shown below. After simple analysis can be drawn, a point P poll method is called after a new head node is completed (shown above head node), then the calling thread can offer normal node is added, the specific process or and as mentioned above. (Then again in the tail when the tail is pointing to the junction of it, in fact, offer to call the method to add elements after completion p.casNext(null, newNode), will come to judge p != t, it will update the location pointed to by the tail after it finished)

    //在最开始时候获得的t=tail
    t=tail; //for循环中赋值t
    //...offer的其他代码
    if(t != (t = tail)) { //这里还是一样:tail为volatile修饰,所以重新读取tail变量
        p = t; //这里表示tail结点不变(按照上图poll执行完后的情况,tail指向位置没有变化,所以p不会被赋值为t)
    } else {
        p = head; //注意这时候的head已经指向的新的首结点
    }
    复制代码

Multi-threaded execution poll, poll

So much analysis, we found that with the offer to stay pit methods, as well as a poll code is not analysis, it is analyzed by the following illustration, look at this code skeleton.

//标记
restartFromHead:
for (;;) {//自旋循环
    for (Node<E> h = head, p = h, q;;) {
        //...other code
        //这是自旋循环体中的一个判断
        else if (p == q)
            continue restartFromHead;
    }
}
复制代码

Suppose now or two threads to execute poll method,

  • In an initial state where the queue is

  • Suppose threadA method to poll, and successful implementation of the if (item != null && p.casItem(item, null))piece, in order to set item1 null, as shown below.

  • But not yet implemented updateHead threadA method, this time after the execution threadB poll, p points to the head of the figure above, the following

  • After threadA execution method updateHead updated to point head, and pointing to his own former head of the next node. Then thread B executed q=p.next, it is to get natural p==qresults, so this time we need to jump to the outer loop to re-obtain the latest head node, and then continue

Methods poll

poll method when removing the header element, using the CAS operation item set for the head node null, then delete queue elements to achieve the effect provided by the head node the flushing head pointing position. This time the original head sentinel node is an isolated node, and will be recycled out. Of course, if the time to find a method to poll the thread head node is modified (in this case said above), it is necessary to jump to the outermost loop reacquire new node.

peek method

Gets head of the queue without deleting the first element, if the queue is empty, returns null. Here is the method of

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;
        }
    }
}
复制代码

It should be noted that the first call to peek method when the sentinel node is deleted, and let the head of the queue node points to the first element in the queue, or null.

size method

Count the number of elements currently in the queue, but because using CAS way possible because another thread to delete or add elements in a concurrent environment leads to inaccurate results.

public int size() {
    int count = 0;
    for (Node<E> p = first(); p != null; p = succ(p))
        if (p.item != null)
            // Collection.size() spec says to max out
            if (++count == Integer.MAX_VALUE)
                break;
    return count;
}
//找到队列中的第一个元素(head指向的item为null的结点不算(就是哨兵结点)),
//没有则返回null
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 method

The parameters passed to the element to remove, if there is the element in the queue to delete the first find, and then returns true, false otherwise

public boolean remove(Object o) {
    if (o != null) { //如果传入参数为null,直接返回false
        Node<E> next, pred = null;
        for (Node<E> p = first(); p != null; pred = p, p = next) {
            boolean removed = false;
            E item = p.item;
            //找到相等的就使用cas设置为null,只有一个线程操作成功
            //别的循环查找是否又别的匹配的obj
            if (item != null) {
                if (!o.equals(item)) {
                    //获取next元素
                    next = succ(p);
                    continue;
                }
                removed = p.casItem(item, null);
            }

            next = succ(p);
            if (pred != null && next != null) // unlink
                pred.casNext(p, next);
            if (removed)
                return true;
        }
    }
    return false;
}
复制代码

Reference from "Art of Java Concurrency"

Guess you like

Origin juejin.im/post/5d4789ca51882519ac307a6f