简介

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     }

初始队列

【JUC源码解析】ConcurrentLinkedQueue

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

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

插入前

【JUC源码解析】ConcurrentLinkedQueue

插入后

【JUC源码解析】ConcurrentLinkedQueue

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

插入前

【JUC源码解析】ConcurrentLinkedQueue

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

【JUC源码解析】ConcurrentLinkedQueue

插入结点,并更新tail指针

【JUC源码解析】ConcurrentLinkedQueue

分支三,tail滞后于head

插入前

【JUC源码解析】ConcurrentLinkedQueue

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

【JUC源码解析】ConcurrentLinkedQueue

 插入后

【JUC源码解析】ConcurrentLinkedQueue

 

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

【JUC源码解析】ConcurrentLinkedQueue

 插入后

【JUC源码解析】ConcurrentLinkedQueue

分支三的来历

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

【JUC源码解析】ConcurrentLinkedQueue

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

【JUC源码解析】ConcurrentLinkedQueue

出队

 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结点未被删除

删除前

【JUC源码解析】ConcurrentLinkedQueue

删除后

 【JUC源码解析】ConcurrentLinkedQueue

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

【JUC源码解析】ConcurrentLinkedQueue

删除后,不变

或者

【JUC源码解析】ConcurrentLinkedQueue

删除后

【JUC源码解析】ConcurrentLinkedQueue

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

删除前

【JUC源码解析】ConcurrentLinkedQueue

向后推进

【JUC源码解析】ConcurrentLinkedQueue

删除后,且更新head结点

【JUC源码解析】ConcurrentLinkedQueue

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

初始,同分支三删除前

【JUC源码解析】ConcurrentLinkedQueue

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

【JUC源码解析】ConcurrentLinkedQueue

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

 

行文至此结束。

 

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

 

相关文章:

  • 2021-07-06
  • 2022-02-17
  • 2021-06-10
  • 2021-08-07
  • 2021-05-27
  • 2021-10-05
  • 2021-06-11
  • 2021-06-17
猜你喜欢
  • 2021-08-11
  • 2021-12-18
  • 2021-08-05
  • 2021-09-29
  • 2021-06-03
  • 2021-10-23
  • 2021-09-16
相关资源
相似解决方案