zoukankan      html  css  js  c++  java
  • 【JUC源码解析】ConcurrentLinkedQueue

    简介

    ConcurrentLinkedQueue是一个基于链表结点的无界线程安全队列。

    概述

    队列顺序,为FIFO(first-in-first-out);队首元素,是当前排队时间最长的;队尾元素,当前排队时间最短的。新元素,从队尾插入;检索元素,从队首开始,Node的next属性保证从队首能遍历到所有的有效元素。由于此队列使用Node的next属性联接在一起,因此不允许有null元素。

    队列的实现具有非阻塞,弱一致性,滞后更新等特点。

    队列维护两个指针,head指针和tail指针,一开始,head和tail都指向“哑巴”结点(Node的item属性为空),新结点从尾部插入,队列向后增长,然而,tail并不总是指向尾部(队列中最后一个元素),head也并不总是指向头部(队列中第一个有效元素)。

    head和tail的更新是独立的,也就是说,tail指针很可能比head指针指向更靠前的结点(当然,此结点是已删除结点,下次更新tail结点后,又跑到head结点后面或同一个),这保证了插入和删除的独立性。

    hop,跳,指的是head或tail结点与第一个或最后一个结点的距离,大于等于2时更新。

    新元素入队时,最后一个结点的next域为null,队列中的所有未删除结点的item域不能为null且从head都可以在O(N)时间内遍历到;

    对于要删除的结点,不是将其引用直接置为null,而是将其item域先置为null(迭代器在遍历时会跳过item为null的结点);允许head和tail滞后更新,即前文提到的head/tail指针并非总是指向队列的头/尾节点。

    head结点(head指针指向的结点)的next域不能指向该结点自身,tail结点的next域可以指向该结点自己。

    源码解析

    Node

    1         volatile E item; // 元素内容
    2         volatile Node<E> next; // 指向下一个结点

    头结点和尾结点

    1     private transient volatile Node<E> head; // 头结点
    2     private transient volatile Node<E> tail; // 尾结点

     入队

     1     public boolean offer(E e) {
     2         checkNotNull(e); // 为空,抛出异常
     3         final Node<E> newNode = new Node<E>(e);
     4 
     5         for (Node<E> t = tail, p = t;;) { // t作为tail的快照,p指向t结点
     6             Node<E> q = p.next; // q是p的next结点
     7             if (q == null) { // q为null,说明p是最后一个结点,可以直接插入
     8                 if (p.casNext(null, newNode)) { // 设置新结点为p的next结点
     9                     if (p != t) // 两跳,更新tail结点
    10                         casTail(t, newNode); // 失败,表示其他线程更新成功了
    11                     return true; // 返回
    12                 }
    13             } else if (p == q) // p结点的next域指向了它自己,此操作在更新head结点时发生,表明此结点及其之前的结点已被删除
    14                 p = (t != (t = tail)) ? t : head; // 如果tail结点已被其他线程更新,则p指向t(tail)结点,否则(tail指针指向已被删除的结点),p指向head结点,跳到头部,指向活着的结点
    15             else
    16                 p = (p != t && t != (t = tail)) ? t : q; // p指向它的next结点(q),并在2跳时检查tail结点是否更新,如果更新,则指向tail结点
    17         }
    18     }

    初始队列

    一开始,head和tail指针均指向“哑元”结点。

    分支一,tail指向最后一个结点,直接插入

    插入前

    插入后

    分支二,tail指向倒数第二个结点

    插入前

    向后推进,直至达到插入的条件

    插入结点,并更新tail指针

    分支三,tail滞后于head

    插入前

    情景1
    tail指针未被其他线程更新

     插入后

    情景2
    tail指针被其他线程更新
     

     插入后

    分支三的来历

    A和B结点被删除,B结点next域指向自己,head指针更新至结点C

     C结点被删除,head指针指向不变
     接着,D结点被删除,head指针更新至结点E,此时,tail结点滞后于head结点,下图即为分支三初始情况

    出队

     1     public E poll() {
     2         restartFromHead: for (;;) {
     3             for (Node<E> h = head, p = h, q;;) { // h作为head的快照,p指向h结点
     4                 E item = p.item; // 获取p的item
     5 
     6                 if (item != null && p.casItem(item, null)) { // 如果item不为null, 表示此结点还未删除,尝试将其删除
     7                     if (p != h) // 两跳,更新一次head结点
     8                         updateHead(h, ((q = p.next) != null) ? q : p);
     9                     return item; // 返回此结点item
    10                 } else if ((q = p.next) == null) { // 否则,表示此结点已被删除,则查看其next结点是否存在
    11                     updateHead(h, p); // 若不存在,则更新head指针指向该结点
    12                     return null; // 并返回null
    13                 } else if (p == q) // 如果此结点已被删除,并且其next结点不为null,而是指向了自己,表示别的线程已经更新了head指针,为了找到有效结点,则从头开始(转向新的head结点)
    14                     continue restartFromHead;
    15                 else // 如果此结点已被删除,并且其next结点存在,向后推进,寻找未被删除结点
    16                     p = q;
    17             }
    18         }
    19     }

    分支一,head结点未被删除

    删除前

    删除后

     

    分支二,head结点已被删除,且其next结点不存在

    删除后,不变

    或者

    删除后

    分支三,head结点已被删除,并且其next结点存在,并非指向自己

    删除前

    向后推进

    删除后,且更新head结点

    分支四,如果此结点已被删除,并且其next结点不为null,而是指向了自己,表示别的线程已经更新了head指针,如分支三

    初始,同分支三删除前

    向后推进p指针时,其他线程抢先删除结点,并更新了head指针,同分支三删除后

    此时,应从头开始,重新获取head指针,进行后续操作。

    行文至此结束。

    尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_clq.html

  • 相关阅读:
    matlab学习笔记之求解线性规划问题和二次型问题
    matlab学习笔记之基础知识(一)
    jQuery中获取特定顺序子元素(子元素种类不定)的方法
    几种常见网页布局设计
    jQuery中删除节点方法remove()、detach()、empty()分析
    jQuery实现复选框全选、全不选、反选问题解析
    window.onload和$(document).ready()比较
    redis+php微博功能的redis数据结构设计总结(四)
    redis+php实现微博功能(三)
    redis+php实现微博功能(二)
  • 原文地址:https://www.cnblogs.com/aniao/p/aniao_clq.html
Copyright © 2011-2022 走看看