【JDK源码笔记】- 非阻塞的无界线程安全队列 —— ConcurrentLinkedQueue
前言
JUC 下面的相关源码继续往下阅读,这就看到了非阻塞的无界线程安全队列 —— ConcurrentLinkedQueue,来一起看看吧。
介绍
基于链接节点的无界线程安全队列,对元素FIFO(先进先出)进行排序。 队列的头部是队列中最长时间的元素,队列的尾部是队列中最短时间的元素。 在队列的尾部插入新元素,队列检索操作获取队列头部的元素。
当许多线程共享对公共集合的访问 ConcurrentLinkedQueue 是一个合适的选择。 与大多数其他并发集合实现一样,此类不允许使用null元素。
基本使用
1 | public class ConcurrentLinkedQueueTest { |
源码分析
基本结构
参数介绍
1 | private static class Node<E> { |
在 ConcurrentLinkedQueue 内部含有一个内部类 Node,如上所示,这个内部类用来标识链表中的一个节点,通过代码可以看出,在 ConcurrentLinkedQueue 中的链表为单向链表
。
1 | public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> |
头尾节点使用 volatile
修饰,保证内存可见性。
构造函数
1 | public ConcurrentLinkedQueue() { |
当创建对象时,头尾节点都是指向一个空节点。
添加元素
1 | public boolean add(E e) { |
画图说明:
- 单线程情况下:
- 当执行到
Node<E> q = p.next;
时,当前情况如图所示:
- 判断
q == null
,满足条件,此时便会执行p.casNext(null, newNode)
使用 CAS 设置 p.next。 - 设置成功之后,
p == t
没有变动,所以程序退出。
- 多线程情况下:
- 当执行到
Node<E> q = p.next;
时,当前情况如图所示:
- 多个线程执行
p.casNext(null, newNode)
使用 CAS 设置 p.next。 - A 线程 CAS 设置成功:
- B 线程 CAS 执行失败, 重新循环,会执行到
p = (p != t && t != (t = tail)) ? t : q
。
- 再次循环就可以成功设置上了。
获取元素
1 | public E poll() { |
画图过程如下:
- 在执行内层循环时,如果队列为空:
E item = p.item;
此时,iterm 为 null,会updateHead(h, p)
并返回 null。 - 假设同时有并发插入操作,添加了一个元素,此时如图所示:
这时会执行最后的 else 将 p = q
- 继续循环获取 item,并执行
p.casItem(item, null)
, 然后判断p != h
,更新 head 并返回 item。
这里的情况比较复杂,这里只是列举一种,如果需要可以自己多列举几种。
而查看元素的代码和获取元素代码类似就不多介绍了。
size 操作
1 | public int size() { |
CAS 没有加锁,所以 size 是不准确的。并且 size 会遍历一遍列表,比较耗费性能。
总结
ConcurrentLinkedQueue 在工作中使用的相对较少,所以阅读相关源码的时候也只是大概看了一下,了解常用 API,以及底层原理。
简单总结就是使用单向链表来保存队列元素,内部使用非阻塞的 CAS 算法,没有加锁。所以计算 size 时可能不准确,同样 size 会遍历链表,所以并不建议使用。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 程序员小航!
评论