Java并发Concurrent包——ConcurrentLinkedQueue源码分析

ConcurrentLinkedQueue 是并发中的高性能队列,而 BlockingQueue 接口下的是并发的阻塞队列。
ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null 元素。

属性

头尾两个指针。

    private transient volatile Node<E> head;
    private transient volatile Node<E> tail;

对于 Node 类,定义了一系列的 CAS 操作:

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

        // 建立新节点
        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);
            }
        }
    }

构造函数

    // 创建一个最初为空的ConcurrentLinkedQueue
    public ConcurrentLinkedQueue() {
        head = tail = new Node<E>(null);
    }
    // 根据给定集合来建立ConcurrentLinkedQueue
    public ConcurrentLinkedQueue(Collection<? extends E> c) {
        Node<E> h = null, t = null;
        // 遍历集合c中的每个元素
        for (E e : c) {
            checkNotNull(e);
            // 对每个元素建立Node节点
            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;
    }

重要方法

offer(E e)

将指定元素插入此队列的尾部。

    public boolean offer(E e) {
        // 检查对象是否为null
        checkNotNull(e);
        // 不为null就可以创建对应的Node节点
        final Node<E> newNode = new Node<E>(e);
        // 一直循环,直到节点新增成功
        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            if (q == null) {                       // 分支1
                // p是最后一个节点的情况,那么可以在其后新加节点
                // CAS操作,如果为null的时候,则增加新节点
                // 这里如果有另一个线程已经增加了新节点,p的后一个节点就不为null了,增加失败继续循环
                // 将t节点的next置为当前节点
                if (p.casNext(null, newNode)) {    // 分支2
                    if (p != t)                    // 分支3
                        // 如果尾节点为t,那么更新为newNode
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
                // Lost CAS race to another thread; re-read next
                // 如果当前线程和另一个线程在上边步骤的casNext没有竞争过的话,继续循环执行即可
            }
            // 如果p和q相等,且都不为null
            else if (p == q)                       // 分支4
                // We have fallen off list.  If tail is unchanged, it
                // will also be off-list, in which case we need to
                // jump to head, from which all live nodes are always
                // reachable.  Else the new tail is a better bet.
                p = (t != (t = tail)) ? t : head;
            else                                   // 分支5
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

根据代码完整的添加几个节点,就知道增加的完整过程了:
1. 队列为空时,第一个元素 A 插入,此时头尾都是初始节点,分支 1 处显然成立,进入分支 2 新增一个节点,此时 p==t 指向同一个节点,不走分支 3。(此时 tail 未变,仍为初始节点,并没有指向新节点 A)。这里 return true,完成一次插入。
2. (1) 插入第二个元素 B,此时尾指针 tail 指向初始节点,而 q=p.next,即 q 指针指向 A 节点。此时显然不走分支 1 和 分支 4,直接走分支 5 ,我们看分支 5 的操作:此时 p 和 t 都指向初始节点,所以执行结果是 p=q ,即 p 指针也指向了 q 指针的位置,节点 A 处。
(2) 继续循环,由于 p=q,那么循环第一句将,q=p.next,p 指针为节点 A,那么 q 显然为 null 。此时分支 1 成立,进行分支 2 的 CAS 操作,显然可以成功添加节点 B 。此时,由于 t 还指向 tail 的初始节点,而 p 指针指向 A,所以二者不想等,进行分支 3 ,这是设置 tail 的 CAS 操作,显然可以成立,将 B 节点设置为 tail 尾节点。return true,完成第二次插入。
3. 插入第三个元素 C ,就和插入元素 A 一样了。

tail不是时刻指向最后一个节点,至少间隔1个节点,才会更新一次tail。

poll()

    public E poll() {
        // 内层的continue,跳到外层继续执行,是为了内层for循环的条件重置的
        restartFromHead:
        // 两层for循环均是无限循环直到成功return
        for (;;) {
            // 定义p,h指针指向head
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;
               // 如果元素不为空而且p处的元素为item的话,将其置为null
                if (item != null && p.casItem(item, null)) {      // 分支1
                    // Successful CAS is the linearization point
                    // for item to be removed from this queue.
                    if (p != h) // hop two nodes at a time        // 分支2
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    // 返回出队列的对象
                    return item;
                }
                else if ((q = p.next) == null) {                   // 分支3
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)                                   // 分支4
                    continue restartFromHead;
                else                                               // 分支5
                    p = q;
            }
        }
    }

仍然根据代码完整的 poll 出几个节点,就知道增加的完整过程了:
比如当前队列中有 A , B , C 三个节点需要出队列。根据上边的入列过程,我们知道 C 节点进来后,tail 指针并没有更新,仍然是指向 B 节点的。(因为没有 D 节点,如果 D 节点入列了,才会将 tail 指向 D,这里可以看成 每入列两个节点 是一个完整的过程。)
1. (1)出列一个节点,定义 p , h 指针指向 head, 假如之前没有进行过出列操作,那么 head 指针是指向初始节点的,为 null,所以,分支 1 不成立,而 q=p.next 显然指向 A 节点不为 null ,分支 3 也不成立,显然分支 4 也不成立,只能走分支 5 ,p=q ,即 p 和 q 一样都指向节点 A 了。
(2)继续内层循环,由于 p 指向节点 A ,那么此时 分支 1 成立,将 A 节点 item 置为 null,此时 p 显然和 h 不相等,继续走分支 2,将 h 指针置为 B 节点。然后 return item,即 A 对象出队列。
2. 再出列一个节点,此时 p, h,head 指针 都指向节点 B,分支 1 成立,将 B 节点的 item 置为 null,此时 p 和 h 相等,跳过分支 2 ,return item,即 B 对象出队列。
3. (1)再出列一个节点,由于 head 指针并没有更新位置,仍然是在 B 节点的位置上(虽然 B 的对象已经出队列了),此时,p,h指针都指向 head,此节点 item 为 null,所以 分支1 不成立,分支 3 显然也不成立,此时 q 指向 C 节点。执行分支 5,将 p 也指向 C 节点。
(2)继续内层循环,由于 p,q 都指向 C 节点,进入分支 1,将 C 节点的 item 置为 null 。 然后判断分支 2,此时 p!=h,所以更新头结点,由于 C 节点没有下一个节点了,所以 head 指针更新到 C 节点上。之后 return,节点 C 出列。
4. 在出列一个节点,由于 head 指向 C 节点的位置,p,h 也一样,此时 分支 3 成立,return null。已经没有可出列的节点了。

关于入队和出队过程中未涉及到的 分支4,是这样的情景:
如果入队元素较少导致 tail 节点更新较慢,同时出队操作较快导致 head 已经指向 tail 之后的节点。这种情况下需要将 p 要么指向最新的 tail 节点(若 tail 节点已经更改),要么指向 head 节点,才能不落后于队列。

此队列利用了 CAS 来完成数据操作,同时允许队列的不一致性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值