ConcurrentLinkedQueue源码分析

概述

ConcurrentLinkedQueue是一个无边界的线程安全的非阻塞队列,遵循FIFO。队列的head节点是时间最长的节点,队列的tail节点是时间最短的节点。新元素将会插入在队列的tail节点之后,而队列的检索操作从队列的head节点开始。像很多并发集合一样,该队列不允许null元素。

该队列的迭代器iterator是弱一致性的,迭代器返回的元素只是反映了队列在迭代器创建时的状态。它们不会抛出ConcurrentModificationException,可以和poll、offer等其他操作同时进行。

happen-before原则:队列的元素插入操作优先于队列的元素访问和删除操作。

构造函数

/**
     * Creates a {@code ConcurrentLinkedQueue}
     * initially containing the elements of the given collection,
     * added in traversal order of the collection's iterator.
     *
     * @param c the collection of elements to initially contain
     * @throws NullPointerException if the specified collection or any
     *         of its elements are 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;
    }

offer()方法

tail节点并不一定是指向队列的最后一个节点,它可能指向最后一个节点的前一个节点。

为了减少更新tail节点的次数,提高入队的效率,Doug Lea并没有让tail节点作为队尾last节点,只有tail节点与last节点之间的距离等于1的时候才需要更新tail节点。即队列每追加2个节点才会更新一次tail指针。

假设最初状态是tail节点等于last节点,则追加的情形如下:

  • 追加第一个节点时,tail节点的next指针为null。走{语句2},更新队列的tail节点的next节点为新节点。但不走{语句3}。
  • 追加第二个节点时,tail节点的next指针不为null。走{语句4}和{语句1},连跳2个节点,然后走{语句3}更新tail节点。此时又回到最初状态 —— tail节点等于last节点。
/**
     * Inserts the specified element at the tail of this queue.
     * As the queue is unbounded, this method will never return {@code false}.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        checkNotNull(e);
        final Node<E> newNode = new Node<E>(e);

        for (Node<E> t = tail, p = t;;) {   //循环初始化p为tail节点
            Node<E> q = p.next;  //语句1处
            if (q == null) {
                // p is last node
//如果q等于null,说明tail节点p是队尾节点
                if (p.casNext(null, newNode)) {  //语句2处
                    // Successful CAS is the linearization point
                    // for e to become an element of this queue,
                    // and for newNode to become "live".
//语句4处设置p=q后则满足条件p!=t
                    if (p != t) // hop two nodes at a time 追加2个节点才更新tail指针
                        casTail(t, newNode);  // Failure is OK.  //语句3处
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
//ConcurrentLinkedQueue允许list的并发修改,当一个线程在迭代这个节点,
//可能另一个节点在删除这个节点,updateHead()方法会将已删除节点的next指针指向它自身。
//此时会出现p等于q的情况。
            else if (p == q)
                // 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.
//如果tail节点没改变,跳到head节点,否则跳到新的tail节点
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;   //语句4处
        }
    }

poll()方法

head节点并不一定是指向队列的第一个有效节点(有效是指节点的item不为null),它可能指向第一个有效节点的前一个无效节点。

为了减少更新head节点的次数,提高出队的效率,Doug Lea让队列每出队2个节点才会更新一次head指针。

假设最初状态是head节点等于队首节点,则出队的情形如下:

  • 出队第一个节点。走{语句1},将head节点的item字段通过cas设置为null。不更新head指针为next节点。
  • 出队第二个节点。走{语句1}发现head节点的item等于null,属于无效节点。走{语句3}将q节点设置为head节点的后继节点,q节点此时即第二个节点。q节点不为null,走{语句4}将p设置为q节点。再走{语句1},将第二个节点的item字段通过cas设置为null。最后走{语句4},更新head指针,设置head指针为第二个节点的next节点,从而连跳2个节点。
public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;
//将head节点的item字段通过cas设置为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 
//如果p节点不等于head节点(一般此时因为走{语句3}和{语句4},p节点等于head节点的后继节点,所以不等)
//并且如果p节点的next指针不为null,通过cas设置head指针为p节点的next节点。
//即此时head指针连跳2个节点
                        updateHead(h, ((q = p.next) != null) ? q : p); //语句2
                    return item;
                }
//将q节点设置为head节点的后继节点,并判断该节点是否为null
                else if ((q = p.next) == null) { //语句3
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)
                    continue restartFromHead;
                else
//将p设置为q节点
                    p = q;   //语句4
            }
        }
    }

Node

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);
            }
        }
    }

 

参考:java并发之ConcurrentLinkedQueue

Simple,fast,practical non-blocking and blocking concurrent-queue algorithms

摘要

我们设计了一个新的非阻塞并发队列算法,和只有2个锁的阻塞队列算法(使入队和出队可以同时进行)。2个算法都是Simple,fast,practical的。

介绍

并发的FIFO队列广泛应用于各种应用和系统当中。为了保证正确性,队列的并发访问必须保证同步。通常来讲,并发数据结构的算法,包括FIFO队列,分为2种:非阻塞和阻塞。在多核环境中,阻塞算法的性能下降更严重,尤其是操作在某个点被挂住或者延迟时。造成延迟的原因可能是处理器调度抢占、页错误、缓存丢失。非阻塞算法在这些面前表现得更加强大。

很多的非阻塞算法都是基于compare_and_swap,因此必须处理ABA问题:

  1. 线程 1 从内存位置V中取出A。
  2. 线程 2 从位置V中取出A。
  3. 线程 2 进行了一些操作,将B写入位置V。
  4. 线程 2 将A再次写入位置V。
  5. 线程 1 进行CAS操作,发现位置V中仍然是A,操作成功。

 算法

图一展示了非阻塞队列的数据结构和操作的伪代码。该算法中的非阻塞队列是一个带有head和tail指针的linkedList。该算法使用compare_and_swap,通过使用计数器counter避免ABA问题。为了允许出队操作可以释放出队节点,出队操作确保tail指针不会指向出队节点和任意前继节点,这意味着出队节点可以安全地被复用。

图一展示了只有2个锁的阻塞队列的数据结构和操作的伪代码。该算法使用2个独立的head锁和tail锁,从而允许入队和出队可以并发进行。跟在非阻塞队列一样,我们使用了一个冗余的节点放在linkedList的开始处。因为这个冗余节点,入队不需要去访问head节点(只访问tail节点),出队不需要去访问head节点(只访问head节点),这样避免了可能的死锁问题。

 

译自:Simple,fast,practical non-blocking and blocking concurrent-queue algorithms

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值