[java队列]——ConcurrentLinkedQueue

ConcurrentLinkedQueue介绍

前面介绍过[java队列]——LinkedBlockingQueue,它是一个可阻塞的线程安全的单链表实现的队列,这里介绍另一个相似又有区别的队列ConcurrentLinkedQueue,它有以下特点

  • 非阻塞队列,不能用于线程池
  • 线程安全,使用CAS+自旋保证
  • 单链表实现

ConcurrentLinkedQueue内部实现

基本属性

public class ConcurrentLinkedDeque<E> extends AbstractCollection<E>implements Deque<E>, java.io.Serializable {
    
    //头结点
    private transient volatile Node<E> head;

    //尾结点
    private transient volatile Node<E> tail;

	private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
    }

结论:

  • 单链表结构

构造方法

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

    public ConcurrentLinkedQueue(Collection<? extends E> c) {
        Node<E> h = null, t = null;
        //遍历结合,将元素添加到单链表中
        for (E e : c) {
            checkNotNull(e);
            Node<E> newNode = new Node<E>(e);
            if (h == null)
                h = t = newNode;
            else {
                t.lazySetNext(newNode);
                t = newNode;
            }
        }
        if (h == null)
            h = t = new Node<E>(null);
        head = h;
        tail = t;
    }

结论:

  • 无参构造,设置头尾指针,结点值为null
  • 传入集合构造,生成初始链表

入队

入队有两个方法add(E e)和offer(E e),因为ConcurrentLinkedQueue是非阻塞队列,因此没有put和offer(timeout)方法。

	public boolean add(E e) {
        return offer(e);
    }
    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,则是待插入的尾结点位置
                if (p.casNext(null, newNode)) {
                    //使用cas设置原尾结点的next指向新结点
                    if (p != t) // hop two nodes at a time
                    	//通过cas设置好p->next指向新结点后,如果当前p不是原尾结点t,则说明有其他线程已经调整修改了尾结点
                    	//这时候p并不是原尾结点t,因此要重新这是尾结点t。
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            else if (p == q)
                //如果p->next = p,说明p已经出队。因此重新设置p得值
                p = (t != (t = tail)) ? t : head;
            else
                // t后面还有值,重新设置p的值(这里说明有其他线程入队了)
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

结论:

  • 通过tail定位到队列的尾部,通过CAS把新结点设置到原尾结点的next结点。
  • 如果在设置完之后,更新tail指向新结点。

出队

与入队一样,ConcurrentLinkedQueue出队一样只有两个方法remove和poll

	public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }
    
	public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;

                if (item != null && p.casItem(item, null)) {
                    //通过cas取出头结点,并更新头结点
                    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;
            }
        }
    }

结论:

  • 通过head定位到头节点,CAS更新其值为null;
  • 如果CAS更新失败或者头节点变化了,就重新寻找头节点,并重试;
  • 使用CAS和自旋出队的时候不会阻塞线程,没找到元素就返回null;

ConcurrentLinkedQueue总结

  • 非阻塞队列
  • 线程安全,使用CAS+自旋控制队列操作
  • 不能用于线程池

与LinkedBlockingQueue做比较

队列线程安全阻塞线程池有界
ConcurrentLinkedQueue安全非阻塞无锁,效率较高,使用CAS+自旋不支持有界
LinkedBlockingQueue安全阻塞重入锁,效率较低支持有界
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值