Jdk1.6 JUC源码解析(24)-ConcurrentLinkedQueue
作者:大飞
功能简介:
- ConcurrentLinkedQueue是一种基于单向链表实现的无界的线程安全队列。队列中的元素遵循先入先出(FIFO)的规则。新元素插入到队列的尾部,从队列头部取出元素。
- ConcurrentLinkedQueue内部采用一种wait-free(无等待)算法来实现。
源码分析:
- 首先看下内部结构:
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable { private static final long serialVersionUID = 196745693267521676L; private static class Node<E> { private volatile E item; private volatile Node<E> next; Node(E item) { // Piggyback on imminent casNext() lazySetItem(item); } E getItem() { return item; } boolean casItem(E cmp, E val) { return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } void setItem(E val) { item = val; } void lazySetItem(E val) { UNSAFE.putOrderedObject(this, itemOffset, val); } void lazySetNext(Node<E> val) { UNSAFE.putOrderedObject(this, nextOffset, val); } Node<E> getNext() { return next; } boolean casNext(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); private static final long nextOffset = objectFieldOffset(UNSAFE, "next", Node.class); private static final long itemOffset = objectFieldOffset(UNSAFE, "item", Node.class); } /** * 表示第一个存活的节点(头节点),访问此节点时间复杂度为O(1)。 * Invariants: * - 所有存活的节点都能通过头结点到达。 * Non-invariants: * - 头节点的元素可以为null。 * - 可以允许尾节点比头节点滞后,也就是说,可能从头节点无法到达尾节点。 */ private transient volatile Node<E> head = new Node<E>(null); /** * 队列的尾节点(唯一的next为null的节点),访问此节点时间复杂度为O(1)。 * Invariants: * - 最后的节点可以通过对tail调用succ()方法访问到。 * Non-invariants: * - 尾节点的元素可以为null。 * - 可以允许尾节点比头节点滞后,也就是说,可能从头节点无法到达尾节点。 * - 尾节点的next可能指向自身。 */ private transient volatile Node<E> tail = head; public ConcurrentLinkedQueue() {}
可见,内部并没有锁,只有一个头节点和一个尾节点。初始时头节点和尾节点都指向同一节点。
- 接下来看一下添加元素到队列的方法:
public boolean add(E e) { return offer(e); } public boolean offer(E e) { if (e == null) throw new NullPointerException(); Node<E> n = new Node<E>(e); retry: for (;;) { Node<E> t = tail; Node<E> p = t; for (int hops = 0; ; hops++) { Node<E> next = succ(p); // 1.获取p的后继节点。(如果p的next指向自身,返回head节点) if (next != null) { // 2.如果next不为null if (hops > HOPS && t != tail) continue retry; // 3.如果自旋次数大于HOPS,且t不是尾节点,跳出2层循环重试。 p = next; // 4.如果自旋字数小于HOPS或者t是尾节点,将p指向next。 } else if (p.casNext(null, n)) { // 5.如果next为null,尝试将p的next节点设置为n,然后自旋。 if (hops >= HOPS) casTail(t, n); // 6.如果设置成功且自旋次数大于HOPS,尝试将n设置为尾节点,失败也没关系。 return true; // 7.添加成功。 } else { p = succ(p); // 8。如果第5步尝试将p的next节点设置为n失败,那么将p指向p的后继节点,然后自旋。 } } } } final Node<E> succ(Node<E> p) { Node<E> next = p.getNext(); //如果p节点的next节点指向自身,那么返回head节点;否则返回p的next节点。 return (p == next) ? head : next; } /** * 允许头尾节点的指针滞后,所以当头尾节点离"实际位置"的距离 * (按节点)小于HOPS时,不会去更新头尾指针。这里是假设volatile写代价比volatile读高。 */ private static final int HOPS = 1;
简单分析一下offer方法里的过程:
1.假设没有竞争的情况下,初始状态时head和tail都指向一个节点,这时来了一个新节点n1,代码走向上面注释第1行,得到的next为null,然后走向第5行,然后将p(也就是tail节点)的next节点设置为n1,成功返回。注意这里并没有调整tail指针,head和tail还是指向之前的节点。然后再来一个新节点n2,还是走向第1行,得到的next是n1,然后走向第8行,将p指向n1,自旋一次,又来到第1行,得到的next为null,然后走向第5行,将p(也就是n1)的next节点设置为n2。由于已经自旋一次,所以这时还会走向第6行,将tail指向n2。
2.有竞争的情况也很好分析,假设在注释第5行竞争失败,那么会走向第8行,将p向队尾推进一步,然后重试;如果重试了多次后(超过一次),在注释第一行获取的next仍然不为空,且同时尾节点也被其他线程推进了,那说明当前节点离尾节点太远了,跳出循环,重新定位尾节点然后再试,这样可以减少自旋次数。
- 再看一下从队列中获取元素的方法:
public E poll() { Node<E> h = head; Node<E> p = h; for (int hops = 0; ; hops++) { E item = p.getItem(); // 1.获取p节点上的元素item。 if (item != null && p.casItem(item, null)) { // 2.如果item不为null,尝试将p的item设置为null。 if (hops >= HOPS) { Node<E> q = p.getNext(); updateHead(h, (q != null) ? q : p); // 3.如果自旋次数大于HOPS,尝试更新头节点。 } return item; // 4.获取元素成功。 } Node<E> next = succ(p); // 5.获取p的后继节点。(如果p的next指向自身,那么返回head节点) if (next == null) { updateHead(h, p); // 6.如果p的后继节点为null,尝试将p设置为头节点,然后跳出循环。 break; } p = next; } return null; // 7.从第6步过来,没有成功获取元素,返回null。 } final void updateHead(Node<E> h, Node<E> p) { if (h != p && casHead(h, p)) h.lazySetNext(h); //将h的next指向自身,帮助GC。 }
和offer方法一样的思路。
再看下peek方法,只获取元素,不移除节点:
public E peek() { Node<E> h = head; Node<E> p = h; E item; for (;;) { item = p.getItem(); if (item != null) break; Node<E> next = succ(p); if (next == null) { break; } p = next; } updateHead(h, p); return item; }
- 继续看一下其他方法:
看其他方法之前,先看一个内部支持方法first:
Node<E> first() { Node<E> h = head; Node<E> p = h; Node<E> result; for (;;) { E item = p.getItem(); if (item != null) { result = p; break; } Node<E> next = succ(p); if (next == null) { result = null; break; } p = next; } updateHead(h, p); // 这里会帮助推进一下头节点。 return result; }可见,和peek里面的逻辑基本一致。但之所以没有利用first来实现peek,而是单独写peek,是因为可以减少一次volatile读。
public boolean isEmpty() { return first() == null; } /** * 注意这个方法内部使用遍历实现,时间复杂度为O(n)。 */ public int size() { int count = 0; for (Node<E> p = first(); p != null; p = succ(p)) { if (p.getItem() != null) { // Collections.size() spec says to max out if (++count == Integer.MAX_VALUE) break; } } return count; } public boolean contains(Object o) { if (o == null) return false; for (Node<E> p = first(); p != null; p = succ(p)) { E item = p.getItem(); if (item != null && o.equals(item)) return true; } return false; } public boolean remove(Object o) { if (o == null) return false; Node<E> pred = null; for (Node<E> p = first(); p != null; p = succ(p)) { E item = p.getItem(); if (item != null && o.equals(item) && p.casItem(item, null)){ Node<E> next = succ(p); if(pred != null && next != null) pred.casNext(p, next); return true; } pred = p; } return false; } public Object[] toArray() { // Use ArrayList to deal with resizing. ArrayList<E> al = new ArrayList<E>(); for (Node<E> p = first(); p != null; p = succ(p)) { E item = p.getItem(); if (item != null) al.add(item); } return al.toArray(); } @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { // try to use sent-in array int k = 0; Node<E> p; for (p = first(); p != null && k < a.length; p = succ(p)) { E item = p.getItem(); if (item != null) a[k++] = (T)item; } if (p == null) { if (k < a.length) a[k] = null; return a; } // If won't fit, use ArrayList version ArrayList<E> al = new ArrayList<E>(); for (Node<E> q = first(); q != null; q = succ(q)) { E item = q.getItem(); if (item != null) al.add(item); } return al.toArray(a); }这些方法都是基于first方法实现,代码很容易看懂,这里就不啰嗦了。
- 最后,ConcurrentLinkedQueue的迭代器也是弱一致的。
小总结一下:
ConcurrentLinkedQueue内部在插入或者移除元素时,不会即时设置头尾节点,而是有一个缓冲期(一个节点长的距离),这样能减少一些CAS操作;其次在设置头尾节点的时候,也不会CAS-Loop直到成功,只尝试一次,失败也没关系,下一次操作或者其他线程在操作时会帮忙推进头尾节点(将头尾指针指向正确位置)。
ConcurrentLinkedQueue的代码解析完毕!
相关推荐
该项目还深入解析了从JDK 5到JDK 8各版本的重要特性,为开发者提供了丰富的代码示例和源码分析。 1. **Java基础用法**: - **变量与数据类型**:Java支持基本数据类型如int、float、char等,以及引用类型如类、...
本篇将深入解析JDK中的JUC包,探讨其核心组件和机制,帮助开发者更好地理解和利用这些高级并发工具。 一、线程池ExecutorService ExecutorService是JUC包中的核心接口,它扩展了Executor接口,提供了管理和控制线程...
│ 高并发编程第二阶段46讲、ClassLoader链接阶段(验证,准备,解析)过程详细介绍.mp4 │ 高并发编程第二阶段47讲、ClassLoader初始化阶段详细介绍clinit.mp4 │ 高并发编程第二阶段48讲、JVM内置三大类加载器...
│ 高并发编程第二阶段46讲、ClassLoader链接阶段(验证,准备,解析)过程详细介绍.mp4 │ 高并发编程第二阶段47讲、ClassLoader初始化阶段详细介绍clinit.mp4 │ 高并发编程第二阶段48讲、JVM内置三大类加载器...
内容概要:本文详细介绍了基于三菱PLC和三菱触摸屏构建的停车场智能管理系统。系统分为入口、出口和管理中心三大部分,分别负责车辆身份识别、车位检测、道闸控制、缴费结算等功能。三菱PLC作为核心控制器,通过梯形图编程实现了车辆检测、道闸控制等关键逻辑;三菱触摸屏提供人机交互界面,支持参数设置、状态监控等功能。文中还讨论了PLC与触摸屏之间的通信配置,以及如何通过物联网技术将系统接入云端。 适合人群:从事智能交通系统开发的技术人员,尤其是熟悉三菱PLC编程和触摸屏应用的工程师。 使用场景及目标:适用于新建或改造停车场项目,旨在提高停车场管理效率和服务质量,减少人工干预,实现智能化运营。 其他说明:文中提供了具体的硬件配置建议、PLC编程实例、触摸屏界面设计指南及通信协议解析,有助于读者快速理解和实施类似项目。
内容概要:本文深入探讨了基于汇川AM401/AM403系列PLC和CODESYS高级编程模式构建的全自动N95口罩机控制系统。该系统涵盖了多个关键技术,包括轴控制(如绝对定位、相对定位)、凸轮同步控制、超声波焊接机控制、放卷张力控制、封边轴焊耳轴随动跟随控制、高速低速切换控制、步进电机精细控制等。此外,还介绍了IT7070系列触摸屏提供的友好交互界面及其产量统计功能。文章详细解析了各部分的具体实现方式,如通过ST语言编写复杂的控制逻辑,利用CAM_Profile生成器动态调整凸轮曲线,以及通过PID算法实现张力控制等。同时,强调了程序的模块化设计和详细的注释,便于维护和扩展。 适合人群:从事自动化生产设备开发的技术人员,尤其是熟悉PLC编程和CODESYS平台的工程师。 使用场景及目标:适用于希望深入了解全自动N95口罩机控制系统设计和实现的专业人士。主要目标是展示如何通过先进的编程技术和控制策略提升口罩生产的效率和质量。 其他说明:文中提到的实际案例和技术细节有助于读者更好地理解和应用相关技术,同时也为类似项目的开发提供了宝贵的参考资料。
内容概要:本文详细介绍了Linux内核移植在嵌入式开发中的重要性及其具体实施步骤。首先,强调了Linux内核移植作为连接硬件与软件桥梁的重要性,特别是在智能穿戴设备、工业自动化控制系统等广泛应用中的角色。文章随后解析了Linux内核移植的主要步骤,包括准备阶段(选择合适的内核版本、获取源码、配置交叉编译环境)、内核源码修改(硬件平台支持、时钟调整、机器码适配)、内核配置(通过make config、make menuconfig或make xconfig进行配置)、内核编译与安装。此外,还探讨了常见的移植问题及其解决方案,如串口打印异常、文件系统挂载故障和驱动适配难题。最后,通过一个具体的ARM架构开发板移植案例,展示了整个移植流程的实际操作,并展望了Linux内核移植技术的发展趋势。 适合人群:具备一定嵌入式开发基础,特别是对Linux内核有一定了解的研发人员和技术爱好者。 使用场景及目标:①帮助开发者理解Linux内核移植的基本概念和流程;②指导开发者在实际项目中进行Linux内核移植,解决常见问题;③为从事嵌入式系统开发的人员提供理论支持和技术参考。 其他说明:Linux内核移植是一项复杂但极具价值的任务,不仅需要扎实的理论知识,还需要丰富的实践经验。随着技术的进步,Linux内核移植技术也在不断发展,未来的方向将更加注重自动化和智能化,以提高移植效率和成功率。建议读者在学习过程中结合实际案例进行练习,逐步积累经验,掌握这一关键技术。
实现全面的系统表征,包括候选项生成、结构检测、参数估计以及动态和静态模型验证。该软件包特别适用于分析具有固有噪声和误差的流动工厂系统,这些系统被建模为受白噪声破坏的二次多项式。 主要特点: 动态数据分析:处理输入和输出的时间序列数据,并验证数据集以进行识别和验证。 结构检测:删除不合适的聚类,并应用AIC和ERR等优化算法来细化模型结构。 参数估计:使用扩展最小二乘(ELS)或受限扩展最小二乘(RELS)计算模型参数。 模型验证:通过残差分析和相关系数评估模型性能。 静态模型仿真:生成静态响应并模拟各种输入条件下的系统行为。 方法概述: 该类包括支持识别过程的几种方法: generateCandidateTerms:构造一个用于系统特征描述的候选术语矩阵。 detectStructure:应用算法精确识别模型结构。 estimateParameters ELS:使用扩展最小二乘法估计动态模型参数。 estimateParameters RELS:使用受限扩展最小二乘法计算参数。 validateModel:分析模型准确性并验证残差行为。 buildStaticResponse:模拟静态模型对不同输入的响应。 displayModel:以文本和面板格式显示已识别的动态模型。 displayStaticModel:展示静态模型及其仿真结果。
内容概要:本文详细介绍了如何使用 COMSOL Multiphysics 对变压器进行时域和频域分析,探讨了磁致伸缩、噪声和洛伦兹力的影响。文中通过具体的代码示例展示了如何设置时域和频域的边界条件,定义磁致伸缩系数,计算洛伦兹力,并通过多物理场耦合模拟变压器的振动和噪声。此外,还讨论了一些常见的仿真技巧和注意事项,如相位对齐、材料非线性特性和边界条件设置等。 适合人群:从事电力系统研究、变压器设计和仿真的工程师和技术人员。 使用场景及目标:适用于希望深入了解变压器内部物理机制及其对外界因素响应的专业人士。通过掌握这些方法,可以优化变压器设计,减少噪声,提升电力系统的稳定性和可靠性。 其他说明:文章不仅提供了理论背景,还给出了实用的代码片段和仿真技巧,帮助读者更好地理解和应用 COMSOL 进行变压器建模。
linux系统~~~~~~~~~~~~~
TheIntroductionOfApache(Apache的有关介绍)
2025免费微信小程序毕业设计成品,包括源码+数据库+往届论文资料,附带启动教程和安装包。 启动教程:https://www.bilibili.com/video/BV1BfB2YYEnS 讲解视频:https://www.bilibili.com/video/BV1BVKMeZEYr 技术栈:Uniapp+Vue.js+SpringBoot+MySQL。 开发工具:Idea+VSCode+微信开发者工具。
内容概要:本文详细介绍了Matlab/Simulink在电气仿真领域的应用,涵盖多个方面。首先讨论了三相逆变器建模的关键参数设置,如载波频率和死区时间。接着探讨了电机控制中PI参数整定的方法,特别是永磁同步电机的矢量控制。对于新能源发电,着重讲解了光伏阵列的MPPT算法及其优化策略。此外,还涉及电力系统仿真的技巧,如自定义变压器模型和故障穿越功能的实现。文中提供了大量实用的代码片段,帮助读者更好地理解和应用这些技术。 适合人群:从事电力电子、电机控制、新能源发电以及电力系统仿真的工程师和技术人员。 使用场景及目标:①快速搭建和优化电力电子设备的仿真模型;②提高电机控制系统的设计效率和性能;③优化新能源发电系统的MPPT算法;④增强电力系统仿真的准确性和可靠性。 其他说明:文章强调了仿真过程中常见的问题及解决方案,提供了丰富的实战经验和技巧,有助于读者在实际工作中少走弯路。同时,鼓励读者利用Simulink自带的案例库进行学习和参考。
MATLAB统计工具箱中的回归分析命令.pptx
NSAC全国重点标准化考试联盟认证试题计算机辅助设计AutoCAD.doc
精灵传信支持在线提交发送短信,查看回复短信,在线购买额度,自定义对接易支付,设置违禁词,支持网站+小程序双端。 环境要求: PHP >= 73 MySQL>=5.6 Nginx>=1.6 系统安装教程 1.导入安装包里的数据库 2.打开.env文件填写数据库信息 3.设置运行目录public 4.设置伪静态thinkphp 后台账号密码分别是admin,123456
1. 插上手机后会自动检测手机是否连接,连接成功后会自动重启; 2. 电脑上有adb 环境; 3. 电脑上装有grep 程序
Matlab-第七讲:编程基础II(-函数-).pptx
内容概要:本文详细介绍了利用遗传算法和免疫算法解决物流配送中心选址问题的方法,并提供了完整的MATLAB源码及注释。文章首先阐述了物流配送中心选址的重要性和挑战,然后重点讲解了适应度函数的设计,包括处理容量约束和超载惩罚。接着介绍了种群初始化、交叉操作、变异操作的具体实现细节,以及如何通过动态调整变异率来避免早熟收敛。此外,还探讨了免疫算法的应用,通过引入抗体浓度机制防止算法陷入局部最优。最后展示了算法的实际效果,包括运输成本的显著降低和车辆满载率的提升。文中提供的代码具有良好的扩展性,能够适应不同的物流网络规模和需求。 适合人群:从事物流管理、运筹优化领域的研究人员和技术人员,特别是对遗传算法、免疫算法感兴趣的开发者。 使用场景及目标:适用于需要优化物流配送中心选址的企业和个人。主要目标是通过合理的数学建模和智能算法,降低运输成本,提高运营效率,实现资源的最佳配置。 其他说明:本文不仅提供理论解释,还包括详细的代码实现和调优建议,帮助读者更好地理解和应用相关算法。同时,代码中预留了多种扩展接口,方便进一步研究和改进。
内容概要:本文详细介绍了一套基于西门子S7-200 PLC的六位密码锁系统的设计与实现。首先介绍了系统的硬件配置,包括六个数字输入点、四个功能键以及三个状态指示灯。接着深入讲解了密码锁的关键代码,如输入检测、密码比对、错误处理和防破解机制。文中还分享了许多实际调试的经验和技术细节,如按键防抖、移位寄存器的应用、指针寻址和循环比较等。此外,作者还讨论了如何优化程序性能,提高系统的稳定性和安全性。 适合人群:具备一定PLC编程基础的技术人员,尤其是从事工业自动化领域的工程师。 使用场景及目标:适用于需要高安全性和可靠性的门禁控制系统,如工厂车间、仓库等场所的安全门管理。主要目标是通过PLC实现一个稳定的六位密码锁系统,防止未经授权的访问。 其他说明:文中提供了详细的代码示例和调试技巧,帮助读者更好地理解和实现该系统。同时,作者还提到未来可能加入指纹识别等高级功能,进一步提升系统的安全性。