ConcurrentLinkedQueue的入队和出队原理详解

入队(offer)

/**
 * 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
 * 入队
 *
 * 队列刚初始化如下:
 *     head
 *  |__null__|
 *     head
 *
 * 入队A元素:
 * t=p=tail,q=p.next,q==null执行①尝试入队
 *     head
 *  |__null__|    null
 *     tail        q
 *      t
 *      p
 *
 * 入队成功:执行②,p==t,不更新tail,直接退出
 *     head
 *  |__null__| ----> |__A__|
 *     tail    next     q
 *      t
 *      p
 *
 *
 * 再入队B:
 * t=p=tail,q=p.next=A,q!=null,执行④p!=q,执行⑥:
 * p==t && t==tail 所以三元结果为q,p=q,第一次循环结束
 *
 *     head
 *  |__null__| ----> |__A__|
 *     tail    next     q
 *      t
 *      p
 *
 * 第一次循环结束,开始第二次循环:q=p.next,q==null,执行①尝试入队
 *
 *     head
 *  |__null__| ----> |__A__|
 *     tail    next     p        q
 *      t
 *
 * 入队成功则执行②:p!=t,需要更新tail,执行③
 *     head                         newNode
 *  |__null__| ----> |__A__| ----> |__B__|
 *     tail    next     p    next     q
 *      t
 *
 * 执行③之后更新tail无论成功与否都返回true
 *     head                         newNode
 *  |__null__| ----> |__A__| ----> |__B__|
 *             next     p    next     q
 *      t                            tail
 *
 *
 * 继续入队C:t=p=tail,q=p.next,q==null,执行①尝试入队
 *     head
 *  |__null__| ----> |__A__| ----> |__B__|   null
 *             next          next    tail     q
 *                                    t
 *                                    p
 * 入队成功则执行②:p==t,不需要更新tail,直接返回
 *     head                                        newNode
 *  |__null__| ----> |__A__| ----> |__B__| ---->  |__C__|
 *             next          next    tail   next    q
 *                                    t
 *                                    p
 *
 *
 * 现在考虑一种情况:在并发情况下,该线程正在offer()时,tail因其他线程的poll()被删了
 * 此时就需要重新定位tail到head位置(tail被删,说明所有元素都出队了,队列为空)
 *
 * 下图为连续出队ABC之后的结果:
 *
 *     <-------------|                          <-----------|         head
 *  |__null__| ------|    |__null__| -----> |__null__| -----|       |__null__|     null
 *                                    next    tail
 *
 * 下图为出队AB,然后该线程想要入队D时候的结果:也就是第一次循环时
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail      next
 *                                             t                  q
 *                                             p
 * t=p=tail,q=p.next,q!=null,准备执行④,但在执行④之前,其他线程把C出队了,导致q=p=null,
 * 所以就要执行⑤,复位tail到head的位置。
 *
 * 如果不复位,入队结点就会插入到tail.next.next位置,新节点更新为tail后,在tail前面会出现一个
 * null值!
 * 错误的结果如下:
 *     <-------------|                        head               ( C )          tail
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__null__| ----> |__D__|
 *                                    next              next              next
 *                                              t                  p               q
 *
 */
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;
        //定位尾结点,tail不一定是尾结点,可能是tail,也可能是tail.next
        //提升了效率,两次入队中,只有一次需要cas操作
        if (q == null) {
            // p is last node
            //q为null,则p为尾结点
            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".
                //首次添加时,p==t,不进行尾结点更新,所以尾结点存在滞后性
                //并发环境,可能存在添加/删除,tail就更难保证正确指向最后结点
                if (p != t) // hop two nodes at a time②
                    //更新尾结点为最新元素
                    casTail(t, newNode);  // Failure is OK.③
                    //如果失败也可以接受,说明有其它线程更新tail成功了
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        //p==q==null,则空队列,说明其他线程此时进行了出队,将q给删了
        //如果tail不是尾结点,此时q又是唯一的队列元素,则tail被删掉了
        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结点
            p = (t != (t = tail)) ? t : head;//⑤
        else
            // Check for tail updates after two hops.
            //推动tail尾结点往队尾移动
            p = (p != t && t != (t = tail)) ? t : q;//⑥
    }
}
出队(poll)
/**
 * 如果head结点元素不为null,则出队head元素,不更新head结点;
 * 如果head结点元素为null,则更新head结点
 *
 * 假设已入队3个元素A、B、C
 * 出队A:
 * p.item==null,然后执行④,q=A,q!=null,然后执行⑦,
 *     head
 *  |__null__| ------> |__A__| -----> |__B__| -----> |__C__|
 *       h      next            next    tail   next
 *       p                q
 *
 *
 * 执行⑦之后,p=q,进行第二次循环:p.item!=null,出队;然后执行②p!=h,执行③更新head:
 * q=p.next==B,B!=null,三元结果为q,移动head=q,同时将原先的头结点设置为自引用
 *
 *     head
 *  |__null__| ------> |__A__| -----> |__B__| -----> |__C__|
 *       h      next            next    tail   next
 *                        q
 *                        p
 *
 * 执行③之后:A出队成功
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__B__| -----> |__C__|
 *       h                            next    tail   next
 *                             p               q
 *
 *
 * 出队B:
 * h=p=head,执行①p.item!=null,直接尝试出队;
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__B__| -----> |__C__|
 *                                    next    tail   next
 *                                             h
 *                                             p
 * 出队成功,执行②,p==h,直接返回,结束。
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail   next
 *                                             h
 *                                             p
 *
 * 出队C:
 * h=p=head,执行①,p.item==null;进而执行④,q=p.next,q!=null;然后执行⑦
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail      next
 *                                             h                 q
 *                                             p
 *
 * 执行⑦,p=q;进入第二轮循环:执行①,p.item=!=null,尝试出队,出队成功执行②,p!=h,
 * 执行③:q=p.next,q==null,三元结果为p,更新head到p,将原head设置为自引用
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail      next
 *                                             h                 q
 *                                                               p
 * 执行③之后结果:
 *     <-------------|                          <-----------|         head
 *  |__null__| ------|    |__null__| -----> |__null__| -----|       |__null__|     null
 *                                    next    tail                      p        q
 *                                             h
 *
 *  此时发现一个问题,因为连续出队,tail被GC了...如果再入队,需要重新定位tail=head
 *  才可以继续入队,详解见入队的图示。
 */
public E poll() {
    //标记
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            //获取head元素
            E item = p.item;
            //如果head元素不为空,则直接cas尝试出队
            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 ②
                    //更新head,如果p.next为null,则head为p,如果p.next!=null,则head为q(p.next)
                    updateHead(h, ((q = p.next) != null) ? q : p);//③
                //返回元素
                return item;
            }
            //p.next == null则空队列,更新head,返回null
            else if ((q = p.next) == null) {//④
                updateHead(h, p);//⑤
                return null;
            }
            //第一轮更新失败(没有竞争过其他线程),跳回标志位重新开始
            else if (p == q)//⑥
                continue restartFromHead;
            //移动head结点为head.next
            else
                p = q;//⑦
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值