【并发容器】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 封装成一个新节点,确保元素不为 null
    final 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 time
                    updateHead(h, ((q = p.next) != null) ? q : p); // 更新头节点
                return item; // 返回已移除的元素
            }
            // 如果 p 的 next 为 null,说明队列为空,更新头节点并返回 null
            else 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. 返回头节点的数据。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE】网络(2)

一、网络编程套接字 1.1 基础概念 【网络编程】指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff1b;当然&#xff0c;我们只要满足进程不同就行&#xff0c;所以即便是同一个主机&#xff0c;只要是不同进程&#xff0c;基于网络来传…

【Java数据类型学习——String】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 打印字符串长度的两种方法字符串String的比较1.用于比较引用的对象是否指向同一个内存地址2.用equa…

基于Spring Boot的校园部门资料管理系统

一、系统背景与目的 随着信息技术的飞速发展&#xff0c;校园信息化建设成为必然趋势。学校各部门在日常工作中积累了大量的资料&#xff0c;包括教学资料、学生档案、科研成果、行政文件等。传统的纸质资料管理方式存在效率低、易丢失、难以检索等问题&#xff0c;无法满足现…

STL 剖析

STL 六大组件 「STL 六大组件的交互关系」 Container 通过 Allocator 取得数据储存空间Algorithm 通过 Iterator 存取 Container 内容Functor 可以协助 Algorithm 完成不同的策略变化Adapter 可以修饰或套接 Functor、Iterator 配置器(allocator) 配置器&#xff1a;负责空间…

企业网络构建:如何满足业务需求与提升效率

企业组网指通过网络将企业内部的各种设备&#xff08;如电脑、打印机和服务器等&#xff09;连接起来&#xff0c;实现资源共享、信息交流与协同办公的过程。要打造一个高效的企业网络&#xff0c;需要从安全性、可靠性、稳定性和性能等多个方面进行综合考虑。以下内容将详细解…

升级thinkphp8最新版本,升级后发现版本不变

升级thinkphp8.0.3最新版本8.1.1&#xff0c;升级后发现版本不变&#xff0c; 更新TP有两个方法 1 全部更新(所有插件都一起更新) composer update 2 只更新TP框架核心 composer update topthink/framework 造成可能有两个原因&#xff0c;一是缓存问题&#xff0c;二是更新…

Cesium进阶教程——自定义图形、外观、绘图基础、现有着色器移植至Cesium、ShadowMapping、视频GIS、模型压平、卷帘

基础必看 WEBGL基础&#xff08;从渲染管线角度解读&#xff09; 参考路线 http://www.xt3d.online/tutorial/further/article.html 自定义图形 https://blog.csdn.net/m0_55049655/article/details/138908327 https://blog.csdn.net/m0_55049655/article/details/140306837 …

理解数据结构 hashtable的简易理解思路

结构图 为了方便演示&#xff0c;下图中分区算法为下标取模 private int hashFun(int id) {//使用 hash并取模return id % size;}Hashtable的结构如图所示&#xff1a;是一个数组&#xff08;元素为各个链表的表头&#xff09; 多个链表组成&#xff0c;也就是说 hashtable 结…

【YashanDB知识库】kettle同步PG至崖山提示no encryption pg_hba.conf记录

【问题分类】数据导入导出 【关键字】数据同步&#xff0c;kettle&#xff0c;数据迁移&#xff0c;pg_hba.conf 【问题描述】使用kettle同步postgresql至崖山数据库时提示以下报错信息&#xff1a; 【问题原因分析】pg_hba.conf 文件中没有正确配置允许从 IP 地址 连接到数…

记录2024-leetcode-字符串DP

10. 正则表达式匹配 - 力扣&#xff08;LeetCode&#xff09;

UE5制作伤害浮动数字

效果演示&#xff1a; 首先创建一个控件UI 添加画布和文本 文本设置样式 添加伤害浮动动画&#xff0c;根据自己喜好调整&#xff0c;我设置了缩放和不透明度 添加绑定 转到事件图表&#xff0c;事件构造设置动画 创建actor蓝图类 添加widget 获取位置 设置位移 创建一个被击中…

【USB-HID】“自动化键盘“

这里写目录标题 【USB-HID】"自动化键盘"1. 前言2. 框架3. 实现3.1 模拟键盘按键输入 【USB-HID】“自动化键盘” 1. 前言 最近从朋友那了解了一种"自动化键盘"&#xff0c;能够通过上位机录制按键脚本&#xff0c;然后执行脚本&#xff0c;实现物理键盘…

STM32F407ZGT6-UCOSIII笔记4:时间片轮转调度

本文学习与程序编写基于 正点原子的 STM32F1 UCOS开发手册 编写熟悉一下 UCOSIII系统的 时间片轮转调度 文章提供测试代码讲解、完整工程下载、测试效果图 目录 解决上文的卡系统问题&#xff1a; 使能时间片轮转调度&#xff1a; 任务初始化定义更改&#xff1a; 文件结构…

【Flask+OpenAI】利用Flask+OpenAI Key实现GPT4-智能AI对话接口demo - 从0到1手把手全教程(附源码)

文章目录 前言环境准备安装必要的库 生成OpenAI API代码实现详解导入必要的模块创建Flask应用实例配置OpenAI API完整代码如下&#xff08;demo源码&#xff09;代码解析 利用Postman调用接口 了解更多AI内容结尾 前言 Flask作为一个轻量级的Python Web框架&#xff0c;凭借其…

搭建springmvc项目

什么是springmvc MVC它是一种设计理念。把程序按照指定的结构来划分: Model模型 View视图 Controller控制层 springmvc框架是spring框架的一个分支。它是按照mvc架构思想设计的一款框架。 springmvc的主要作用: 接收浏览器的请求数据&#xff0c;对数据进行处理&#xff0c;…

Three.js相机Camera控件知识梳理

原文&#xff1a;https://juejin.cn/post/7231089453695238204?searchId20241217193043D32C9115C2057FE3AD64 1. 相机类型 Three.js 主要提供了两种类型的相机&#xff1a;正交相机&#xff08;OrthographicCamera&#xff09;和透视相机&#xff08;PerspectiveCamera&…

为“行车大脑”降温:Simdroid-EC助力汽车ECU设计研发

ECU&#xff08;Electronic Control Unit&#xff0c;电子控制单元&#xff09;被誉为汽车的行车大脑&#xff0c;在工作时会产生大量的热量&#xff0c;而其散热存在以下难题&#xff1a;一是工作环境恶劣&#xff0c;ECU常处于高温环境中&#xff1b;二是ECU所处的空间较为狭…

改进系列(6):基于DenseNet网络添加TripletAttention注意力层实现的番茄病害图像分类

目录 1. DenseNet 介绍 2. TripletAttention 3. DenseNet TripletAttention 4. 番茄场景病害病虫识别 4.1 数据集情况 4.2 训练 4.3 训练结果 4.4 推理 1. DenseNet 介绍 DenseNet是一种深度学习架构&#xff0c;卷积神经网络&#xff08;CNN&#xff09;的一种变体&…

Ubuntu 20.04LTS 系统离线安装5.7.44mysql数据库

Ubuntu 20.04LTS 系统离线安装5.7.44mysql数据库 环境下载 MySQL 5.7.44 包安装标题检查服务是否启动成功遇到的问题登陆&修改密码&远程访问 环境 操作系统&#xff1a;Ubuntu 20.04.4 LTS 数据库&#xff1a;MySQL 5.7.34 内核版本&#xff1a;x86_64&#xff08;amd…

0基础学前端-----CSS DAY6

0基础学前端-----CSS DAY6 视频参考&#xff1a;B站Pink老师 今天是CSS学习的第六天&#xff0c;今天开始的笔记对应Pink老师课程中的CSS第三天的内容。 本节重点&#xff1a;CSS的三大特性以及CSS的盒子模型。 1.CSS的三大特性 CSS有三个重要特性&#xff1a;层叠性、继承性…