【高并发系列】20、JDK并发容器 - ConcurrentLinkedQueue

ConcurrentLinkedQueue使用链表作为其数据结构,节点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);
    }
    // 设置当前Node的item值,cmp为期望值,val为设置目标值;
    // 当当前值等于期望值时,将目标设置为val;
    boolean casItem(E cmp, E val) {
        return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }
    
    void lazySetNext(Node<E> val) {
        UNSAFE.putOrderedObject(this, nextOffset, val);
    }
    // 设置next字段值,cmp为期望值,val为设置目标值;
    // 当当前值等于期望值时,将目标设置为val;
    boolean casNext(Node<E> cmp, Node<E> val) {
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    ...
}

ConcurrentLinkedQueue类内部两个重要字段,head和tail,分别表示头部和尾部;

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

无参构造方法初始化head和tail:

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

 向队列添加元素,方法没有锁操作,线程安全由CAS操作和队列的算法来保证;

/**
 * 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;;) {
        Node<E> q = p.next;
        if (q == null) {
            // p is last node
            if (p.casNext(null, newNode)) {
                // Successful CAS is the linearization point
                // for e to become an element of this queue,
                // and for newNode to become "live".
                if (p != t) // hop two nodes at a time
                    casTail(t, newNode);  // Failure is OK.
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        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.
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

核心是for循环,只有添加成功后返回true才能退出循环;

当第一次加入元素时,队列为空,所以p.next为null,进入第一层判断,表明p是最后一个节点,CAS尝试将p的next节点赋值为newNode,即将第一个元素加入队列;如果casNext()失败,循环尝试,直至成功;如果成功,此时p==t,直接返回true,退出方法;所以新增第一个元素时,tail不会被更新;

当增加第二个元素时,tail没有被更新,p=t=tail=head,tail还指向head节点位置,所以p.next实际指向的是第一个元素,此时q!=null&p!=q,所以跳转到最后else语句中,因为p==t,给p赋新值,即p=q,p实际指向了第一个元素,此时进入下一层循环时,p.next为null,进入第一层判断,CAS尝试将p的next节点赋新值newNode,如果casNext()失败,循环尝试直到成功;如果成功,此时p指向第一个元素,而t指向head节点,所以p!=t,尝试更新tail,即将tail由t值所在位置(此时为head节点)更新为新加入元素(第二个元素);

当p==q时,此节点为哨兵节点,next指向自己,这个节点已经不在队列中存在了,表示要删除的节点或空节点;此时无法通过next获得后续节点,所以要返回head节点(所有的节点都可以从head节点获得);一旦在执行过程中发生tail节点被其他线程修改的情况,则使用新的tail作为链表末尾,避免了从head节点重新查找tail的开销;

“!=”不是原子操作,可以被中断,在执行“!=”操作时,会先取得t值,再执行t=tail,取得t的新值,然后再进行比较两个值是否相等;

p = (t != (t = tail)) ? t : head;

哨兵节点产生情况:

ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.add("1");
queue.poll();
queue.add("2");

poll()方法源码如下:

扫描二维码关注公众号,回复: 5358664 查看本文章
public E poll() {
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;

            if (item != null && p.casItem(item, null)) {
                // Successful CAS is the linearization point
                // for item to be removed from this queue.
                if (p != h) // hop two nodes at a time
                    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;
        }
    }
}

队列中只有1个元素,此时tail并没有更新,指向和head相同的位置;此时head本身的item为null,next指向第一个元素;

在第一次循环时,代码直接进入最后一个else语句,p=q,此时p、q指向第一个元素;

第二轮循环中,p.item为“1”,不为null,如果CAS操作成功,进入第一个if语句中,p.casItem()将p的item设置为null(删除操作,将第一个元素置空);此时h指向head,而p指向刚刚置空的第一个元素,所以不相等;p.next为null,赋值null给q,((q = p.next) != null) ? q : p)返回p,进入updateHead(h,p)方法;

/**
 * Tries to CAS head to p. If successful, repoint old head to itself
 * as sentinel for succ(), below.
 */
final void updateHead(Node<E> h, Node<E> p) {
    if (h != p && casHead(h, p))
        h.lazySetNext(h);
}

可以看到,通过casHead(h,p)将p作为新的链表头部,而原有的head就被h.lazySetNext(h)设置为哨兵节点;

此时原有的head头部和tail实际上是同一个元素,因此,再次用offer()方法插入元素时,就会遇到这个哨兵节点;

猜你喜欢

转载自blog.csdn.net/hellboy0621/article/details/87832567