并发编程之 ConcurrentLinkedQueue 源码剖析
前言
今天我们继续分析 java 并发包的源码,今天的主角是谁呢?ConcurrentLinkedQueue,上次我们分析了并发下 ArrayList 的替代 CopyOnWriteArrayList,这次分析则是并发下 LinkedArrayList 的替代 ConcurrentLinkedQueue, 也就是并发链表。
Demo

该类继承结构如下:

该类是 Collection 框架下的实现。也就是Java 类库提供的数据结构。
add 方法将指定元素插入此队列的尾部。
poll 方法 获取并移除此队列的头,如果此队列为空,则返回 null。
peek 方法 获取但不移除此队列的头;如果此队列为空,则返回 null。
那么我们就看看 doug lea 是如何实现并发安全的吧。在这之前,我们可以试想一下,实现并发安全无非两种方式,一种是锁,就像我们之前分析的容器,比如 concurrentHashMap,CopyOnWriteArrayList , LinkedBolckingQueue,还有一种是 CAS,在这些容器里也用到了。那么,如果是我们来实现这个队列,使用什么方式呢?有趣的问题。
开始看源码吧。
add 方法源码剖析
实际上是调用 offer 方法,add 方法是 Collection 接口规定的容器方法,而 offer 方法是 Queue 接口的方法。

那我们就看看 offer 方法:
public boolean offer(E e) {
// 检查是否是null,如果是null ,抛出NullPointerException
checkNotNull(e);
// 创建一个node 对象,使用 CAS 创建对象
final Node<E> newNode = new Node<E>(e);
// 轮询链表节点,知道找到节点的 next 为null,才会进行赋值
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// 找到null值之后将刚刚创建的值通过CAS放入
if (p.casNext(null, newNode)) {
// 因为 p 遍历在轮询后会变化,因此需要判断,如果不相等,则使用CAS将新节点作为尾部节点。
if (p != t)
casTail(t, newNode); // Failure is OK.
// 放入成功后返回 ture
return true;
}
}
// 轮询后 p 有可能等于 q,此时,就需要对 p 重新赋值。
else if (p == q)
// 这里需要注意一下:判断t != t,是因为并发下可能 tail 被改了,如果被改了,则使用新的 t,否则从链表头重新轮询。
p = (t != (t = tail)) ? t : head;
else
// 同样,当 t 不等于 p 时,说明 p 在上面被重新赋值了,并且 tail 也被别的线程改了,则使用新的 tail,否则循环检查p的下个节点
p = (p != t && t != (t = tail)) ? t : q;
}
}
代码行数很少,楼主注释也写了,这里可以看到 doug lea 使用了 CAS 的方式防止并发错误,同时,也看得出对 tail 变量被修改的担忧,通过 t != t 的判断,来检查 tail 是否被其他线程修改了,而这个offer 操作,如果不成功,则永远不会返回,这个队列同时也是无界的。这点在使用的时候需要注意一下。
那么 poll 方法如何实现呢?
poll 方法源码剖析
public E poll() {
// 循环跳出标记,类似goto
restartFromHead:
// 死循环
for (;;) {
// 死循环,从 head 开始遍历
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
// 如果 head 不是null 且 将 head 的 item 属性设置为null成功,则返回并更新头节点
if (item != null && p.casItem(item, null)) {
// 如果 p != h 说明在 p 轮询时被修改了
if (p != h)
// 如果p 的next 属性不是null ,将 p 作为头节点,而 q 将会消失
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
// 如果 p(head) 的 next 节点 q 也是null,则表示没有数据了,返回null,则将 head 设置为null
// 注意: updateHead 方法最后还会将原有的 head 作为自己 next 节点,方便offer 连接。
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
// 如果 p == q,说明别的线程取出了 head,并将 head 更新了。就需要重新开始
else if (p == q)
// 从头开始重新循环
continue restartFromHead;
// 如果都不是,则将 h 的 next 赋给 h,并重新循环。
else
p = q;
}
}
}
上面楼主已经写了注释,但是有一个非常困扰哦楼主的疑点,就是 else if (p == q) 这行代码,楼主分析的没有问题,但是再楼主的单线程测试这段代码时,出现了诡异的情况,无法解释,因此, 楼主贴出测试用例,大家一起看看:
测试代码:

断点代码:

注意,断点位置一定要和我的一致。会出现一些奇怪的效果。楼主无法解释,因为这个问题,楼主一直都不敢发这篇文章出来,但楼主觉得有必要说出这个问题,抛砖引玉。
问题在于:单线程怎么会进入这段代码?按道理,但线程是不会出现这个情况的。
总结
这次的源码分析让楼主很痛苦,网上很多的文章也无法解释这是为什么,希望有高人能告诉楼主,到底是怎么回事?
并发编程之 ConcurrentLinkedQueue 源码剖析的更多相关文章
- 并发编程之 CopyOnWriteArrayList 源码剖析
前言 ArrayList 是一个不安全的容器,在多线程调用 add 方法的时候会出现 ArrayIndexOutOfBoundsException 异常,而 Vector 虽然安全,但由于其 add ...
- 并发编程之 ThreadLocal 源码剖析
前言 首先看看 JDK 文档的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局 ...
- 并发编程之 LinkedBolckingQueue 源码剖析
前言 JDK 1.5 之后,Doug Lea 大神为我们写了很多的工具,整个 concurrent 包基本都是他写的.也为我们程序员写好了很多工具,包括我们之前说的线程池,重入锁,线程协作工具,Con ...
- 并发编程之 AQS 源码剖析
前言 JDK 1.5 的 java.util.concurrent.locks 包中都是锁,其中有一个抽象类 AbstractQueuedSynchronizer (抽象队列同步器),也就是 AQS, ...
- 并发编程之 Exchanger 源码分析
前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
- 并发编程之 Semaphore 源码分析
前言 并发 JUC 包提供了很多工具类,比如之前说的 CountDownLatch,CyclicBarrier ,今天说说这个 Semaphore--信号量,关于他的使用请查看往期文章并发编程之 线程 ...
- 并发编程之 CyclicBarrier 源码分析
前言 在之前的介绍 CountDownLatch 的文章中,CountDown 可以实现多个线程协调,在所有指定线程完成后,主线程才执行任务. 但是,CountDownLatch 有个缺陷,这点 JD ...
- 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟
1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...
随机推荐
- git命令行的操作实例教程
Git 常用命令常用命令 创建新仓库 创建新文件夹,打开,然后执行 git init 1 以创建新的 git 仓库. 检出仓库 执行如下命令以创建一个本地仓库的克隆版本: git clone /pat ...
- EBS 定义显示总帐快码设置
自定义一个功能如下,挂到菜单上就可以了功能 用户功能名 表单 参数GL_GLXDQMLK(自定义) 总帐代码列表 定义代码 VIEW_APPLICATION="SQLGL" HEL ...
- await和async在C#一般处理程序(ashx)中的使用
public class hello : HttpTaskAsyncHandler, IReadOnlySessionState { public IFetchServise fetch { get; ...
- 源自KPI交谈的思考
说明白一件事情不容易 前言 跟领导谈及下半年KPI的时候,问我什么打算/计划,在交谈过程中,有几个有意思的点 问题 Q: 目标是hold住服务端,那么怎么样才算hold住服务端? Q: 如何推动别人去 ...
- ASP.NET MVC5 高级编程-学习日记-第二章 控制器
2.1 控制器的角色 MVC模式中的控制器(Controller)主要负责响应用户的输入,冰球在响应时修改模型(Model).通过这种方式,MVC模式中的控制器主要关注的是应用程序流.输入数据的处理, ...
- powerviot report cannot refresh data
配置完成powerviot后发现打开excel无法刷新数据源连接提示出错: 在security token service服务应用中新建application,如图创建,然后将excel里面的auth ...
- Spring IOC 容器源码分析 - 获取单例 bean
1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...
- 【mock】后端不来过夜半,闲敲mock落灯花 (mockjs+Vuex+Vue实战)
mock的由来[假] 赵师秀:南宋时期的一位前端工程师 诗词背景:在一个梅雨纷纷的夜晚,正处于项目编码阶段,书童却带来消息:写后端的李秀才在几个时辰前就赶往临安度假去了,!此时手头仅有一个简单 ...
- sudoer命令各参数含义及设置
对于普通用户sudo加权的时限制可以执行的命令 https://segmentfault.com/a/1190000007394449 需要修改/etc/sudoers 1. 字段说明如下 授权用户/ ...
- Elasticsearch集群搭建及使用Java客户端对数据存储和查询
本次博文发两块,前部分是怎样搭建一个Elastic集群,后半部分是基于Java对数据进行写入和聚合统计. 一.Elastic集群搭建 1. 环境准备. 该集群环境基于VMware虚拟机.CentOS ...