Java---并发容器之ConcurrentLinkedQueue

25 篇文章 0 订阅
11 篇文章 0 订阅

一.概述

ConcurrentLinkedQueue是一个基于连接节点的无界线程安全队列(java并没有提供构造方法来指定队列的大小,因此它是无界的)。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。为了提高并发量,它通过使用更细的锁机制,使得在多线程环境中只对部分数据进行锁定,从而提高运行效率,通常ConcurrentLikedQueue性能好于BlockingQueue。

二.结构

ConcurrentLinkedQueue内部持有2个节点:head头结点,负责出列, tail尾节点,负责入列。而元素节点Node,使用item存储入列元素,next指向下一个元素节点。都是使用用volatile进行修饰的,以保证内存可见性。

private transient volatile Node<E> head;
private transient volatile Node<E> tail;
private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
		.......
}

三.方法

方法返回值描述
add(E e) boolean将指定元素插入此队列的尾部。
contains(Object o) boolean如果此队列包含指定元素,则返回 true。
isEmpty() boolean如果此队列不包含任何元素,则返回 true。
iterator()Iterator<E>返回在此队列元素上以恰当顺序进行迭代的迭代器。
offer(E e) boolean将指定元素插入此队列的尾部。
peek() E获取但不移除此队列的头;如果此队列为空,则返回 null。
poll()E获取并移除此队列的头,如果此队列为空,则返回 null。
remove(Object o) boolean从队列中移除指定元素的单个实例(如果存在)。
size() int返回此队列中的元素数量。
toArray() Object[]返回以恰当顺序包含此队列所有元素的数组。
toArray(T[] a) 

<T>  T[ ]

返回以恰当顺序包含此队列所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

 

四.线程安全

ConcurrentLinkedQueue是如何保证线程安全的???CAS+自旋操作

(1)入队 offer

 public boolean offer(E e) {
   // 0.这里是检查入队列前要插入的节点是不是为空
        checkNotNull(e);
       // 1. 这是入队前,先创建一个入队节点
        final Node<E> newNode = new Node<E>(e);
		//2. 这里是一个死循环,如果入队不成功继续尝试入队
        for (Node<E> t = tail, p = t;;) {
        //3.这里是判断尾节点给q,然后进行判空
            Node<E> q = p.next;
            if (q == null) {
                // 4. p表示尾节点,下面这句表示看尾节点的下一个节点是不是为空,
//casNext就表示P的下一个节点如果为空,就新建一个节点给尾节点的下一个节点(这个节点就是要入队的新节点),如果不为空,就不用管(说明已经有节点了)
                if (p.casNext(null, newNode)) {
//5.这个是判断尾节点和tail节点是不是指向的是同一个节点,这个是更新tail节点
                    if (p != t) // hop two nodes at a time
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
            }
            //(这里其实又是另外一种场景)说明p指向的节点的next也指向它自己,这种节点称之为哨兵节点,这种节点在队列中存在的价值不大,一般表示为要删除的节点或者是空节点
            else if (p == q)
            
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

入队列主要围绕着两步:

1. 把入队节点当成当前队列尾节点的下一个节点
2. 更新tail节点:(1)如果tail节点的下一个节点不为空,说明已经有了一个要入队的节点了,此时则将此时的入队节点设置为tail节点 ,例如下图中的添加元素1的图 (2)如果tail节点的下一个节点为空,例如添加元素2的图中,此时则将要入队列的节点设置为tail的下一个节点
3. 补充:因为如果在多线程的情况下,可能A线程刚完成第一步的操作,此时B线程过来了,看到tail节点已经发生了变化,这是A线程要准备操作第二更新tail节点的时候,此时A线程就会暂停入队操作,然后需要通过CAS重新获取尾节点。

(2)出队 poll

public E poll() {
    // 如果出现p被删除的情况需要从head重新开始
    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)
                // 当一个线程在poll的时候,另一个线程已经把当前的p从队列中删除——将p.next = p,p已经被移除不能继续,需要重新开始
                continue restartFromHead;
            else
                p = q;
        }
    }
}

final void updateHead(Node<E> h, Node<E> p) {
    if (h != p && casHead(h, p))
        h.lazySetNext(h);
}

出队列的几个步骤:

1. 如果当前head,h和p指向的节点的Item不为null的话,说明该节点即为真正的队头节点(待删除节点),只需要通过casItem方法将item域设置为null,然后将原来的item直接返回即可。

2. 如果当前head,h和p指向的节点的item为null的话,则说明该节点不是真正的待删除节点,那么应该做的就是寻找item不为null的节点。通过让q指向p的下一个节点(q = p.next)进行试探,若找到则通过updateHead方法更新head指向的节点以及构造哨兵节点(通过updateHead方法的h.lazySetNext(h))。

五.ConcurrentLinkedQueue和LinkedBlockingQueue

首先二者都是线程安全的得队列,都可以用于生产与消费模型的场景。

  • ConcurrentLinkedQueue非阻塞队列,采用 CAS+自旋操作,解决多线程之间的竞争,多写操作增加冲突几率,增加自选次数,并不适合多写入的场景。当许多线程共享访问一个公共集合时,ConcurrentLinkedQueue 是一个恰当的选择。从此方面来讲,ConcurrentLinkedQueue更适用于单线程插入,多线程取出,即单个生产者与多个消费者
  • LinkedBlockingQueue阻塞队列,其好处是:多线程操作共同的队列时不需要额外的同步,由于具有插入与移除的双重阻塞功能,对插入与移除进行阻塞,队列会自动平衡负载,从而减少生产与消费的处理速度差距,其阻塞是基于锁机制实现的,导致用户态与内核态切换频繁,消耗系统资源。从此方面来讲,LinkedBlockingQueue更适用于多线程插入,单线程取出,即多个生产者与单个消费者

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值