JUC并发工具七-ConcurrentLinkedQueue和ConcurrentLinkedDeque

目录

 

1 队列概述

2 ConcurrentLinkedQueue

2.1 offer方法

​2.2 poll方法

2.3 remove方法

2.4 peek方法

2.5 element方法

3 ConcurrentLinkedDeque

4 debug源码的坑


1 队列概述

线程安全的并发队列主要有两个,ConcurrentLinkedQueue和ConcurrentLinkedDeque。区别是前者是单向队列,只能FIFO,后者是双向队列,可以FIFO也可以FILO
这里先说一下队列的常用方法
add:将指定的元素插入到此队列中(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用空间,则抛出 IllegalStateException。
offer:将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
put:将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。

remove:移除并返回元素,若队列为空,则抛异常。
poll:移除并返回元素,若队列为空,返回null。
take:若队列为空,发生阻塞,等待有元素。

element:返回队列头部的元素, 如果队列为空,则抛出一个NoSuchElementException异常
peek:返回队列头部的元素,如果队列为空,则返回null


Concurrent系列的队列由于不是阻塞队列,所以没有实现put、take方法。并且该队列是基于链表实现的无界队列,理论上不会说没有空间。所以add和offer方法是一样的,不会抛异常

2 ConcurrentLinkedQueue

2.1 offer方法

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) {
            // 如果q为null,则p是尾节点,cas方式把新的节点追加到p的后面
            if (p.casNext(null, newNode)) {
                if (p != t)
                    //如果p不等于t,说明已经走过了(p = (p != t && t != (t = tail)) ? t : q;)这一步,这里需要重新设置一下尾节点
                    casTail(t, newNode);
                return true;
            }
        }
        else if (p == q)
            // 在更新头结点(updateHead)的时候会出现头结点自引用的情况
            p = (t != (t = tail)) ? t : head;
        else
            //p有next节点,表示p的next节点是尾节点,则需要重新更新p后将它指向next节点
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

一般情况下队列设计的时候尾节点实时指向最新添加节点,但是针对ConcurrentLinkedQueue是每添加两个元素维护把tail节点指向一次尾节点,目的是减少casTail(t, newNode)操作,提高性能,具体流程如图


2.2 poll方法

public E poll() {
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;
            // 如果item不为空则把head的item掷空
            if (item != null && p.casItem(item, null)) {
                if (p != h)
                    // 如果p不是head节点则说明已经走过了p = q步骤,进入内循环的第二次循环,需要更新头结点
                    updateHead(h, ((q = p.next) != null) ? q : p);
                // 直接返回item元素
                return item;
            }
            else if ((q = p.next) == null) {
                // 如果p节点的next为null,则说明p节点是头结点,更新头结点为p
                updateHead(h, p);
                return null;
            }
            else if (p == q)
                continue restartFromHead;
            else
                // 如果item == null则把p指向下一个节点重新开始
                p = q;
        }
    }
}

final void updateHead(Node<E> h, Node<E> p) {
    if (h != p && casHead(h, p))
        // 将旧的头结点的next指向头结点
        h.lazySetNext(h);
}

一般情况下队列设计的时候头节点实时指向第一个节点,但是针对ConcurrentLinkedQueue是每移除两个元素维护把head节点指向一次第一个元素,目的是减少updateHead(t, newNode)操作,提高性能,具体流程如图

2.3 remove方法

public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}


remove方法比较简单,这里直接用的是AbstractQueue中的方法

2.4 peek方法

public E peek() {
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;
            if (item != null || (q = p.next) == null) {
                // 如果item不为空或者头结点的后继节点为空则表示p是第一个节点,更新为头结点并返回元素
                updateHead(h, p);
                return item;
            }
            else if (p == q)
                // 如果p
                continue restartFromHead;
            else
                // 如果head节点为空则向后走
                p = q;
        }
    }
}

peek方法比较简单,就是获取第一个元素,同时更新下头结点

2.5 element方法

public E element() {
    E x = peek();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}


element方法比较简单,这里直接用的是AbstractQueue中的方法

3 ConcurrentLinkedDeque

ConcurrentLinkedDeque队列是双向队列,在原有队列的基础上增加了如下方法,感兴趣的同学可以读一下源码,
和ConcurrentLinkedQueue类似,笔者就不再详细记录了
pollFirst
pollLast
removeFirst
removeLast
offerFirst
offerLast
addFirst
addLast

4 debug源码的坑

有兴趣的同学可以debug下源码,看一下offer和poll的具体执行过程,便于更好的理解源码。坑爹的是idea调试的时候有个坑,第一次offer会把head指向新节点,tail节点自引用,导致我一度怀疑我的代码认知,也是折腾了一天才搞好,调试的时候可以参考如下文章配置一下idea:debug ConcurrenLinkedQueue的坑

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值