ConcurrentLinkedQueue源码解析

217 阅读2分钟

1. 介绍

​ 无界的线程安全的队列,FIFO,无锁+链表算法实现,不阻塞

2. 源码

2.1 Node<E> 静态内部类

​ item: 当前节点元素

​ next: 下一个节点

​ 设置元素使用无锁算法实现

2.2 构造函数

    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;
    }

2.2 offer

public boolean offer(E e) {
        //检查节点是否为null
        checkNotNull(e);
        // 创建新节点
        final Node<E> newNode = new Node<E>(e);

        //死循环 直到成功为止
        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            // q == null 表示 p已经是最后一个节点了,尝试加入到队列尾
            // 如果插入失败,则表示其他线程已经修改了p的指向
            if (q == null) {                                // --- 1
                // casNext:t节点的next指向当前节点
                // casTail:设置tail 尾节点
                if (p.casNext(null, newNode)) {             // --- 2
                    // node 加入节点后会导致tail距离最后一个节点相差大于一个,需要更新tail
                    if (p != t)                             // --- 3
                        casTail(t, newNode);                    // --- 4
                    return true;
                }
            }
            // p == q 等于自身
            else if (p == q)                                // --- 5
                // p == q 代表着该节点已经被删除了
                // 由于多线程的原因,我们offer()的时候也会poll(),如果offer()的时候正好该节点已经poll()了
                // 那么在poll()方法中的updateHead()方法会将head指向当前的q,而把p.next指向自己,即:p.next == p
                // 这样就会导致tail节点滞后head(tail位于head的前面),则需要重新设置p
                p = (t != (t = tail)) ? t : head;           // --- 6
            // tail并没有指向尾节点
            else
                // tail已经不是最后一个节点,将p指向最后一个节点
                p = (p != t && t != (t = tail)) ? t : q;    // --- 7
        }
    }

初始化

image.png 添加第一个元素A

​ p = t = tail, q = p.next = null,第一个if判断成功,p.next = A

image.png

添加第二个元素B

​ p = t = tail, q = p.next != null, 走最后一个else,p = q,

​ q = p.next, q == null,走第一个if

image.png

后面插入依次是这个过程

2.3 poll

   public E poll() {
        // 如果出现p被删除的情况需要从head重新开始
        restartFromHead:        // 这是什么语法?真心没有见过
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                // 节点 item
                E item = p.item;
                // item 不为null,则将item 设置为null
                if (item != null && p.casItem(item, null)) {                  // --- 1
                    // p != head 则更新head
                    if (p != h)                                               // --- 2
                        // p.next != null,则将head更新为p.next ,否则更新为p
                        updateHead(h, ((q = p.next) != null) ? q : p);        // --- 3
                    return item;
                }
                // p.next == null 队列为空
                else if ((q = p.next) == null) {                              // --- 4
                    updateHead(h, p);
                    return null;
                }
                // 当一个线程在poll的时候,另一个线程已经把当前的p从队列中删除——将p.next = p,p已经被移除不能继续,需要重新开始
                else if (p == q)                                              // --- 5
                    continue restartFromHead;
                else
                    p = q;                                                    // --- 6
            }
        }
    }
 final void updateHead(Node<E> h, Node<E> p) {
        if (h != p && casHead(h, p))
            h.lazySetNext(h);
    }

在updateHead中h指向了自己,所以在offer中,会有p==q的情况,代表节点已经被删除了

此时,head在tail前面,只能重新指向head,然后找新tail

p = (t != (t = tail)) ? t : head;