[java队列]——ConcurrentLinkedQueue

  • ConcurrentLinkedQueue介绍
  • ConcurrentLinkedQueue内部实现
  • 基本属性
  • 构造方法
  • 入队
  • 出队
  • ConcurrentLinkedQueue总结
  • 与LinkedBlockingQueue做比较


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

安全

阻塞

重入锁,效率较低

支持

有界