ConcurrentLinkedQueue源码分析整理

前面的几篇文章对List和Map的实现都做了整理和分析。这篇文章我们再简要地看一下ConcurrentLinkedQueue的源码实现。需要注意,ConcurrentLinkedQueue的适合场景是高并发情况下,在低并发或者单线程情况下,更适合我们的是(可以考虑用锁维护的)传统的链表队列。

0. ConcurrentLinkedQueue简介

从类名上我们就可以看得出来,Concurrent保证了并发中的线程安全,Linked提示是链表实现,Queue则说明是一个队列。

从类的声明上也验证了这点:

public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
        implements Queue<E>, java.io.Serializable

ConcurrentLinkedQueue这个类实现的是一个无界的,保证了先进先出的队列,队列元素中不可以放置null元素(内部实现的特殊节点除外)。

另外,我们需要注意的是ConcurrentLinkedQueue锁保持的只是特定场景下的一个“快照”,不能“精确”地维护队列当前情况下的“真相”。下面详细解释这句说明。

1. add() 和 offer() 源码粗略分析

两者实际上是一回事,因为add()直接调用了offer()方法。源码如下:

    public boolean offer(E e) {
        checkNotNull(e);
        final Node newNode = new Node(e);

        for (Node t = tail, p = t;;) {
            Node q = p.next;
            if (q == null) {
                if (p.casNext(null, newNode)) {

                    if (p != t)
                        casTail(t, newNode);
                    return true;
                }

            }
            else if (p == q)
                p = (t != (t = tail)) ? t : head;
            else
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

实际上就是在链表的尾部( q == null 的情况)进行插入操作,通过sun.misc.Unsafe的CAS比较并设置的原子方法,对tail属性的的next对象进行修改,如果成功则返回,如果不成功则继续循环。

除了 q == null 的情况外,还有两个条件,这三个条件构成了互斥关系。

我们发现,如果按照单线程来执行,当结尾成功加入新的数据节点时,p还是等于t的,那么最后新加入的结点就不可能成为tail,即tail属性没有被更新,可以看出这个类的实现确实是“不精确的”。

那么只有当第二个来增加结点的线程进入第一个条件并添加结点成功的时候,tail才有机会得到更新。当然由于此时tail.next已经不是null了,会走一遍第三个条件。

至于第二个条件 p == q ,我考虑再三,应该是考虑对链表的其它操作造成的,即导致 p.next == p ,否则仅凭这个方法 p == q 是不可能的。

另外,这其中只有第一个条件的执行会导致方法返回,否则不断循环。在第二个和第三个条件中,也都有对tail(被其它线程)改变的判断并更新tail属性。

2. poll() 和 peek() 源码粗略分析

首先说下,poll()和peek()都是取队列头元素,区别在于前者会删除节点而后者不会。

我们就看下poll()方法:

public E poll() {
        restartFromHead:
        for (;;) {
            for (Node h = head, p = h, q;;) {
                E item = p.item;

                if (item != null && p.casItem(item, null)) {
                    if (p != h)
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }

其实,大体逻辑是和offer()方法类似的,在头结点的数据被置为null之后,head并不在当前线程得到及时更新,而是等下一次修改来完成这个任务。

如上两段代码说明,这个类真得是更适合高并发场景,而不适合低并发和非并发场景。但纵观全类的代码实现,并未用到synchronized和Lock,仅仅通过CAS的原子操作保证了并发场景下的线程安全,这也就保证了队列实现的高效性。

到这里为止,以作者我本人目前的情况,很难将实现的细节分析得更透彻更详细,本文所能做的就是让大家知道ConcurrentLinkedQueue这个类的大概实现方案,知道适用场景和需要注意的点。

这个类实现的理论基础是Maged M. Michael 和 Michael L. Scott.的《Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms》。这是一篇描述高效非阻塞并发队列的论文,原文地址如下,需要了解更多细节的朋友可以到这里参考并推敲其巧妙的原理。

http://www.cs.rochester.edu/u/scott/papers/1996_PODC_queues.pdf

此条目发表在 Java, Java语言, 并发, 开发, 计算机技术 分类目录,贴了 , , , 标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>