【并发容器】ConcurrentLinkedQueue:优雅地实现非阻塞式线程安全队列

实现一个线程安全的队列有两 种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁 (入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。非阻塞的实现方 式则可以使用循环CAS的方式来实现。

1. 简介

ConcurrentLinkedQueue 是一个 非阻塞(lock-free) 的线程安全队列,它适合在并发场景下使用。(并发不是特别剧烈)

  • 数据结构:基于 单向链表
  • 核心原理:通过 CAS(Compare-And-Swap) 操作确保线程安全,避免锁的性能开销。
  • 应用场景
    • 适合多线程环境中频繁进行 入队(offer)出队(poll) 操作,但并发又不是特别激烈。
    • 在不需要阻塞等待的场景下,优于阻塞式队列(如 BlockingQueue)。
    • 不需要强一致性。

特点

1. ConcurrentLinkedQueue 是 无界队列,并且遵循 FIFO(先进先出) 原则,如果没有适当的控制机制,可能会导致内存溢出

2. size()isEmpty() 方法可能不准确:在并发环境下使用 size()isEmpty() 方法时需要特别小心,因为它们的结果可能并不准确。如果需要精确的元素数量或空队列检测,建议使用额外的同步机制或原子变量来实现

2. 核心原理:非阻塞实现的关键 - CAS 算法

2.1. 什么是 CAS?

CAS(Compare-And-Swap) 是一种常用的 无锁同步机制,主要用于实现原子操作。CAS 的核心思想是:

  1. 比较当前变量的值是否等于预期值。
  2. 如果相等,则将变量更新为新值。
  3. 如果不相等,操作失败,重新尝试。

优点:避免加锁带来的线程阻塞和上下文切换,性能更高。

缺点:CAS 可能导致 ABA 问题(即值从 A 变成 B,又变回 A),但 ConcurrentLinkedQueue 通过引用地址判断有效地解决了这个问题。

2.2. Wait-Free 算法

ConcurrentLinkedQueue 基于 Michael & Scott 算法 实现了一个 非阻塞式队列,并在此基础上进行了一些优化。

  • Wait-Free 的含义是:操作一定会在有限步内完成,不会出现无限等待的情况。
  • 关键技术:通过 CAS 操作 来确保对队列的入队和出队操作是线程安全的。

3. ConcurrentLinkedQueue 源码分析

下面看看大师Doug Lea是如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的,核心是采用了"wait-free"算法(即CAS算法)来实现,该算法在 Michael&Scott算法上进行了一些修改。

3.1. 数据结构:单向链表

ConcurrentLinkedQueue 使用 单向链表 作为底层数据结构:每个节点使用内部类 Node<E> 表示,链表中维护两个重要的指针:head:指向链表的头节点,tail:指向链表的尾节点。

private transient volatile Node<E> head; // 头节点
private transient volatile Node<E> tail; // 尾节点static class Node<E> {volatile E item; // 节点存储的数据volatile Node<E> next; // 指向下一个节点的引用Node(E item) {this.item = item;}
}

3.2. 入队操作:offer(E e)

入队操作的目标是将新节点插入到链表的尾部,核心是使用 CAS 来更新尾节点的引用。

/*** 将指定元素插入队列的尾部。* 该方法采用非阻塞的CAS算法,保证线程安全且高效。* 由于队列是无界的,因此该方法总是返回 true。** @param e 要插入的元素,不能为 null* @return {@code true} (队列是无界的,插入一定成功)* @throws NullPointerException 如果插入的元素为 null*/
public boolean offer(E e) {// 1. 将元素 e 封装成一个新节点,确保元素不为 nullfinal Node<E> newNode = new Node<E>(Objects.requireNonNull(e));// 2. 开始自旋,尝试将新节点插入到队列的尾部for (Node<E> t = tail, p = t;;) { // t 为尾节点,p 用于遍历节点Node<E> q = p.next; // q 指向当前节点 p 的下一个节点// 3. 如果当前节点 p 的 next 为 null,说明 p 是尾节点if (q == null) {// 尝试将 p 的 next 指向新节点,使用 CAS 操作确保线程安全if (NEXT.compareAndSet(p, null, newNode)) { // CAS 成功,说明新节点已被成功插入队列// 如果 p 不是当前的尾节点 t,尝试更新尾节点为新节点if (p != t) { TAIL.weakCompareAndSet(this, t, newNode); // 尾节点更新不强制成功}return true; // 插入成功,返回 true}// 如果 CAS 失败,说明有其他线程插入了新节点,继续循环重试}// 4. 如果 p == q,说明链表结构发生了异常(例如节点被标记为已删除)else if (p == q) { // 此时需要重新定位到队列的尾部,确保链表结构完整p = (t != (t = tail)) ? t : head; // 尝试从尾部 t 或头部 head 开始重新遍历}// 5. 否则,说明 p 不是尾节点,继续向后遍历链表else {// 如果 p 不等于 t 并且尾节点 t 发生了更新,则更新 t 并继续遍历// 否则,p 指向当前节点的下一个节点 q,继续循环p = (p != t && t != (t = tail)) ? t : q;}}
}

3.3. 出队操作:poll()

出队操作的目标是移除并返回头节点的数据。

public E poll() {// 标记重新从头部开始循环的标签restartFromHead: for (;;) {// 从头节点开始遍历链表for (Node<E> h = head, p = h, q;; p = q) {final E item;// 如果当前节点的 item 不为 null,并且 CAS 成功(移除该 item)if ((item = p.item) != null && p.casItem(item, null)) {// 成功的 CAS 操作是该元素被移除的线性化点// 如果 p 不是头节点 h,跳过两个节点进行操作if (p != h) // hop two nodes at a timeupdateHead(h, ((q = p.next) != null) ? q : p); // 更新头节点return item; // 返回已移除的元素}// 如果 p 的 next 为 null,说明队列为空,更新头节点并返回 nullelse if ((q = p.next) == null) {updateHead(h, p); // 更新头节点为当前节点return null; // 返回 null 表示队列为空}// 如果 p == q,表示发生了循环,重新从头开始处理else if (p == q)continue restartFromHead; // 重新从头开始遍历}}
}

步骤解析

  1. 通过 CAS 更新 head 指针,使其指向下一个节点。
  2. 将头节点中的数据置空(通过 casItem 操作)。
  3. 返回头节点的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/64385.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink CDC 读取oracle库数据性能优化

通过综合考虑Oracle数据库配置、Flink作业配置以及其他优化措施&#xff0c;可以显著提升Flink CDC读取Oracle库数据的性能和效率。可以从以下几个方面进行&#xff1a; 一、Oracle数据库配置优化 ‌开启归档日志‌&#xff1a; 通过执行sqlplus /assysdba或sqlplus/nolog命令…

SpringAop-拦截参数带注解的方法

拦截方法中参数类型为String 且带有Crypto注解的方法&#xff1a;execution(* *(..,Crypto (String),..)) 拦截方法中参数上带有Crypto注解的方法&#xff1a;execution(* *(..,Crypto (*),..)) ..&#xff1a;零个或者多个 *&#xff1a;通配符 样例 /*** 针对带有Crypto…

selenium获取请求头

【原创】Selenium获取请求头、响应头-腾讯云开发者社区-腾讯云 selenium 4.0.0 selenium-wire 5.1.0 python 3.10 from seleniumwire import webdriver import time from selenium.webdriver.common.by import By import re def get_request_headers(driver):"""…

【C++移动语义与完美转发】左值右值,引用,引用折叠,移动语义,万能引用与完美转发

前言 nav2系列教材&#xff0c;yolov11部署,系统迁移教程我会放到年后一起更新&#xff0c;最近年末手头事情多&#xff0c;还请大家多多谅解。本期是一个鸽了半年的教程&#xff0c;很早以前我就一直想写一篇文章有关C的移动语义&#xff0c;一直拖到现在()&#xff0c;那么今…

暂停一下,给Next.js项目配置一下ESLint(Next+tailwind项目)

前提 之前开自己的GitHub项目&#xff0c;想着不是团队项目&#xff0c;偷懒没有配置eslint&#xff0c;后面发现还是不行。eslint的存在可以帮助我们规范代码格式&#xff0c;同时 ctrl s保存立即调整代码格式是真的很爽。 除此之外&#xff0c;团队使用eslint也是好处颇多…

iOS 应用的生命周期

Managing your app’s life cycle | Apple Developer Documentation Performance and metrics | Apple Developer Documentation iOS 应用的生命周期状态是理解应用如何在不同状态下运行和管理资源的基础。在 iOS 开发中&#xff0c;应用生命周期管理的是应用从启动到终止的整…

Hadoop学习笔记(包括hadoop3.4.0集群安装)(黑马)

Hadoop学习笔记 0-前置章节-环境准备 0.1 环境介绍 配置环境&#xff1a;hadoop-3.4.0&#xff0c;jdk-8u171-linux-x64 0.2 VMware准备Linux虚拟机 0.2.1主机名、IP、SSH免密登录 1.配置固定IP地址&#xff08;root权限&#xff09; 开启master&#xff0c;修改主机名为…

扩展SpringBoot中的SpringMVC的默认配置

SpringBoot默认已经给我们做了很多SpringMVC的配置&#xff0c;哪些配置&#xff1f; 视图解析器ViewResolver静态资料的目录默认首页index.html图标名字和图标所在目录&#xff0c;favicon.ico类型转换器Converter&#xff0c;格式转换器的Formatter消息转换器HttpMessageCon…

企业内训|阅读行业产品运营实战训练营-某运营商数字娱乐公司

近日&#xff0c;TsingtaoAI公司为某运营商旗下数字娱乐公司组织的“阅读行业产品运营实战训练营”在杭州落下帷幕。此次训练营由TsingtaoAI资深互联网产品专家程靖主持。该公司的业务骨干——来自内容、市场、业务、产品与技术等跨部门核心岗位、拥有8-10年实战经验的中坚力量…

Android Room 数据库使用详解

一、Room介绍 Android Room 是 Google 提供的一个 Android 数据持久化库&#xff0c;是 Android Jetpack 组成部分之一。它提供了一个抽象层&#xff0c;使得 SQLite 数据库的使用更为便捷。通过 Room&#xff0c;开发者可以轻松地操作数据库&#xff0c;不需要直接编写繁琐的…

双目测距中的鼠标操作回调函数

参考&#xff1a;【OpenCV】双目测距&#xff08;双目标定、双目校正和立体匹配&#xff09; /*****描述&#xff1a;鼠标操作回调函数定义*****/ static void onMouse(int event, int x, int y, int, void*) {if (selectObject){selection.x MIN(x, origin.x);selection.y …

Kaggler日志--Day7

进度24/12/17 昨日复盘&#xff1a; 尝试自己爬取了两个学校的就业信息数据&#xff0c;比较简单但是顺通了爬虫流程 看别人的代码&#xff1a;AQX的。 今日进度&#xff1a; 分析理解昨天代码的过程&#xff0c;统计问题 过程理解 EDA部分 对于不同变量类型判别的举例说明…

NDRCContextUnmarshall断点函数分析之I_RpcBindingCopy函数的作用

NDRCContextUnmarshall断点函数分析之I_RpcBindingCopy函数的作用 第一部分&#xff1a; void RPC_ENTRY NDRCContextUnmarshall ( // process returned context OUT NDR_CCONTEXT PAPI *phCContext,// stub context to update IN RPC_BINDING_HANDLE hRPC, …

IS-IS协议

IS-IS协议介绍 IS-IS&#xff08;Intermediate System to Intermediate System&#xff09;协议是一种链路状态的内部网关协议&#xff08;IGP&#xff09;&#xff0c;用于在同一个自治系统&#xff08;Autonomous System, AS&#xff09;内部的路由器之间交换路由信息。IS-I…

QoS分类和标记

https://zhuanlan.zhihu.com/p/160937314 1111111 分类和标记是识别每个数据包优先级的过程。 这是QoS控制的第一步&#xff0c;应在源主机附近完成。 分组通常通过其分组报头来分类。下图指定的规则仔细检查了数据包头 &#xff1a; 下表列出了分类标准&#xff1a; 普通二…

电机控制杂谈(23)——共模电压与轴电流

1.共模电压与轴电流的关系和危害 对于电压源换流器&#xff0c;由于功率半导体器件的快速开关和PWM调制方案&#xff0c;将在电机定子绕组的中性点&#xff08;N&#xff09;和接地点&#xff08;O&#xff09;之间产生高频共模电压&#xff08;Common-mode voltage&#xff0…

FPGA设计-使用 lspci 和 setpci 调试xilinx的PCIe 问题

目录 简介 lspci lspci-TV lspci-vvv 注意事项 lspci -vs lspci -vvvs 设置pci 识别setpci中的寄存器 setpci -s 00:01.0 d0.b42 简介 lspci 和 setpci 命令在 Linux 发行版中本身可用。该命令具有各种级别的输出&#xff0c;并提供非常有用的时间点查看 PCI 总线…

vue+node+mysql8.0,详细步骤及报错解决方案

1.下载需要安装的插件 下载express npm install express下载cors&#xff0c;用于处理接口跨域问题 npm install cors下载mysql npm install mysql 2.配置服务器 可以在vue项目的src同级创建server文件夹&#xff08;这里的位置可随意选择&#xff09; 然后依次创建&#…

【人工智能】因果推断与数据分析:用Python探索数据间的因果关系

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 因果推断是数据科学领域的一个重要方向,旨在发现变量间的因果关系,而不仅仅是相关性。本篇文章将从因果推断的理论基础出发,介绍因果关系的定义与建模方法,涵盖因果图(Causal Graph)、d-分离、反事实估计等…

并发修改导致MVCC脏写问题

并发修改导致MVCC脏写问题 一、概要 1.1 业务场景 数据库表结构设计&#xff1a; 一个主档数据&#xff0c;通过一个字段&#xff0c;逗号分隔的方式去关联其他明细信息的id。 如主档数据A&#xff0c;有3条明细数据与A关联&#xff0c;其id分别是1,2,3&#xff0c;那么其存…