前言

今天我们继续分析 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 源码剖析的更多相关文章

  1. 并发编程之 CopyOnWriteArrayList 源码剖析

    前言 ArrayList 是一个不安全的容器,在多线程调用 add 方法的时候会出现 ArrayIndexOutOfBoundsException 异常,而 Vector 虽然安全,但由于其 add ...

  2. 并发编程之 ThreadLocal 源码剖析

    前言 首先看看 JDK 文档的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局 ...

  3. 并发编程之 LinkedBolckingQueue 源码剖析

    前言 JDK 1.5 之后,Doug Lea 大神为我们写了很多的工具,整个 concurrent 包基本都是他写的.也为我们程序员写好了很多工具,包括我们之前说的线程池,重入锁,线程协作工具,Con ...

  4. 并发编程之 AQS 源码剖析

    前言 JDK 1.5 的 java.util.concurrent.locks 包中都是锁,其中有一个抽象类 AbstractQueuedSynchronizer (抽象队列同步器),也就是 AQS, ...

  5. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  6. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  7. 并发编程之 Semaphore 源码分析

    前言 并发 JUC 包提供了很多工具类,比如之前说的 CountDownLatch,CyclicBarrier ,今天说说这个 Semaphore--信号量,关于他的使用请查看往期文章并发编程之 线程 ...

  8. 并发编程之 CyclicBarrier 源码分析

    前言 在之前的介绍 CountDownLatch 的文章中,CountDown 可以实现多个线程协调,在所有指定线程完成后,主线程才执行任务. 但是,CountDownLatch 有个缺陷,这点 JD ...

  9. 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟

    1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...

随机推荐

  1. git命令行的操作实例教程

    Git 常用命令常用命令 创建新仓库 创建新文件夹,打开,然后执行 git init 1 以创建新的 git 仓库. 检出仓库 执行如下命令以创建一个本地仓库的克隆版本: git clone /pat ...

  2. EBS 定义显示总帐快码设置

    自定义一个功能如下,挂到菜单上就可以了功能 用户功能名 表单 参数GL_GLXDQMLK(自定义) 总帐代码列表 定义代码 VIEW_APPLICATION="SQLGL" HEL ...

  3. await和async在C#一般处理程序(ashx)中的使用

    public class hello : HttpTaskAsyncHandler, IReadOnlySessionState { public IFetchServise fetch { get; ...

  4. 源自KPI交谈的思考

    说明白一件事情不容易 前言 跟领导谈及下半年KPI的时候,问我什么打算/计划,在交谈过程中,有几个有意思的点 问题 Q: 目标是hold住服务端,那么怎么样才算hold住服务端? Q: 如何推动别人去 ...

  5. ASP.NET MVC5 高级编程-学习日记-第二章 控制器

    2.1 控制器的角色 MVC模式中的控制器(Controller)主要负责响应用户的输入,冰球在响应时修改模型(Model).通过这种方式,MVC模式中的控制器主要关注的是应用程序流.输入数据的处理, ...

  6. powerviot report cannot refresh data

    配置完成powerviot后发现打开excel无法刷新数据源连接提示出错: 在security token service服务应用中新建application,如图创建,然后将excel里面的auth ...

  7. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  8. 【mock】后端不来过夜半,闲敲mock落灯花 (mockjs+Vuex+Vue实战)

      mock的由来[假]   赵师秀:南宋时期的一位前端工程师 诗词背景:在一个梅雨纷纷的夜晚,正处于项目编码阶段,书童却带来消息:写后端的李秀才在几个时辰前就赶往临安度假去了,!此时手头仅有一个简单 ...

  9. sudoer命令各参数含义及设置

    对于普通用户sudo加权的时限制可以执行的命令 https://segmentfault.com/a/1190000007394449 需要修改/etc/sudoers 1. 字段说明如下 授权用户/ ...

  10. Elasticsearch集群搭建及使用Java客户端对数据存储和查询

    本次博文发两块,前部分是怎样搭建一个Elastic集群,后半部分是基于Java对数据进行写入和聚合统计. 一.Elastic集群搭建 1. 环境准备. 该集群环境基于VMware虚拟机.CentOS ...