CocurrentLinkedQueue

170 阅读2分钟

原理

本质是Queue的实现类,用于多线程的容器,底层使用的是链表。(数据存储其实本质只有两种:连续的和非连续的,及链表和数字,为什么没有ConcurrentArrayQueue,因为数组的大小是固定的,在此基础上进行cas很麻烦)

Offer 添加方法

 // 添加任务到队列尾部
  public boolean offer(E e) {
        //1检查是否非空  
        checkNotNull(e);
        final Node<E> newNode = new Node<E>(e);
        //无限循环,获取尾结点t
        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            //初始化队列一开始的时候next节点为null
            if (q == null) {
                //使用cas插入节点
                if (p.casNext(null, newNode)) {
                    // 当队列循环遍历发现尾结点和一开始取的尾节点不一致时,将最新插入的节点设置为尾结点。  
                    //一次移动两个节点,防止操作过于频繁
                    if (p != t) 
                        casTail(t, newNode); 
                    return true;
                }
             
            }
            else if (p == q) //当移除节点时会将next指向自己(哨兵节点),这时就要从头结点依次向后找到尾节点
                p = (t != (t = tail)) ? t : head;
            else
                 // cas多重检测并依次的next找到尾结点
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }
 
 总结:offer的本质就是往链表的尾节点加数据,核心还是cas
       1.检测数据是否为空
       2.无线for循环,保证数据一定被插入,q = tail.next
       3.如果q == null 说明当前节点就是尾结点,使用casNext将新Node加入尾。
       if (p != t)casTail(t, newNode);每2个节点更新下Tail节点(防止频繁CAS)
       else if (p == q) 当前节点是哨兵节点(poll时会让next指向自己),无法从尾部找,就从头节点往后找
       else p = (p != t && t != (t = tail)) ? t : q; 获取最新的尾节点,如果不同就用最新的,相同就变为nextNode

Poll移除

 public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;
                //当item!=null,将item取出
                if (item != null && p.casItem(item, null)) {
                    //没2次更新头节点
                    if (p != h)
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                //当前节点的下一个节点是null,表示已经到了尾节点
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                //哨兵节点,从头再来
                else if (p == q)
                    continue restartFromHead;
                //获取NextNode
                else
                    p = q;
            }
        }
    }
    
final void updateHead(Node<E> h, Node<E> p) {
        //将head更新为P,原来的h.nextNode指向自己
        if (h != p && casHead(h, p))
            h.lazySetNext(h);
    }
    
 void lazySetNext(Node<E> val) {
 	    // 存储变量的引用到对象的指定的偏移量处
 	    // 有延迟,非立即可见
            UNSAFE.putOrderedObject(this, nextOffset, val);
}   
总结:poll 从头节点移除
    1.先从头节点获取item,如果不是null,尝试cas修改,头节点每2次更新
    2.如果是null判断下一个节点是否是null,如果是表示到了末尾,return null
    3.如果是哨兵节点就从头再来
    4.一般情况下如果 item==null,获取当前节点的下一个节点