并发容器之ConcurrentLinkedQueue

在并发编程中,有时候需要使用线程安全的队列。如果要实现一个线程安全的队列有两种方式:

①阻塞算法:加锁

②非阻塞算法:CAS

于是乎,Doug  Lea大神编写了ConcurrentLinkedQueue,一个基于链表的线程安全队列。它采用先进先出的规则对节点进行排序,当我们添加一个元素时,它会添加到尾部;当我们删除一个元素时,它会返回队列的头部元素。

一、ConcurrentLinkedQueue的结构(JDK1.8)

ConcurrentLinkedQueue主要由head节点和tail节点组成,head和tail都是一个静态内部类Node,每个Node由item和下一个节点next组成,默认情况head和tail相等。

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

二、ConcurrentLinkedQueue的方法剖析

①offer(E e)

offer方法用来入队(添加元素),入队列就是将入队节点添加到队列的尾部。下面是offer方法的源代码:

public boolean offer(E e) {
    //检查是否为null 此处证明ConcurrentLinkdQueue不可以存放null值
    checkNotNull(e);
    //将新元素封装为Node
    final Node<E> newNode = new Node<E>(e);
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            //如果p是tail节点,把新元素赋值到p.next,采用CAS
            if (p.casNext(null, newNode)) {
                //若为首次添加元素,不会设置tail,设置tail存在滞后性
                //从而减少CAS设置tail的时间
                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
        }
        //由于tail的滞后性,可能出队时会删除tail节点,
        //因此给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.
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
            //移动tail
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

下面我们通过一个例子来说明ConcurrentLinkedQueue的offer方法,向一个ConcurrentLinkedQueue添加张三、李四和王五三个元素。

在jdk1.6时,ConcurrentLinkedQueue采用的是HOPS设计,采用一个hops变量来控制并减少tail节点的更新频率,其实就是tail设置的滞后性,这个常量默认为1,和现在p != t的设计相同。(ps:这里就得说一下方腾飞作者的《Java并发编程的艺术》,都2022年了,jdk都17出啦,不更新一波第二版??虽然这本书还是非常值得看)

②poll()

出队列就是返回头节点的元素。下面是poll方法的源代码:

public E poll() {
    //跳转的tag
    restartFromHead:
    //CAS循环
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;
            //CAS将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;
        }
    }
}
final void updateHead(Node<E> h, Node<E> p) {
    if (h != p && casHead(h, p))
        //懒加载自己指向自己 等待JVM回收
        h.lazySetNext(h);
}

我们还是通过上边添加元素的例子,逐个删除张三、李四、王五。

至此全部删除完成,删除的大致操作就是,获取头节点的item,看item是不是为空,为空则向下移动,移动head节点也是有一个节点的滞后性。

但是,此时tail指向一个null节点,会被jvm所回收,如果我们再添加一个节点呢?假设我们再添加一个节点"赵六"。

三、总结

ConcurrentLinkedQueue的介绍就到此结束啦,在此做一个总结:

①ConcurrentLinkedQueue不可以插入null值

②ConcurrentLinkedQueue并不是每次入队都更新tail节点,也不是每次出队都更新head节点,具有一个节点的滞后性,JDK1.6之前使用了一个HOPS的变量控制滞后性,1.7以后使用的是节点是否相等控制滞后性。

③ConcurrentLinkedQueue是非阻塞的,采用了CAS来控制并发实现线程安全。

今天的分享就到此结束啦,喜欢的小伙伴记得点赞呦。

参考资料:《Java并发编程的艺术》

关注公众号JavaGrowUp,下期不迷路,获取更多精彩内容。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值