【JUC】浅析ConcurrentLinkedQueue

【JUC】浅析ConcurrentLinkedQueue

文章目录

  • 【JUC】浅析ConcurrentLinkedQueue
    • 一、前言
    • 二、ConcurrentLinkedQueue的结构
    • 三、入队列
      • 3.1、入队列的过程
      • 3.2、定位尾节点
      • 3.3、设置入队节点为尾节点
      • 3.4、HOPS的设计意图
    • 四、出队列

一、前言

在并发编程中,有时候需要使用线程安全的队列。如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。非阻塞的实现方式则可以使用循环CAS的方式来实现。本节让我们一起来研究一下DougLea是如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的,相信从大师身上我们能学到不少并发编程的技巧。

ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部;当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法(即CAS算法)来实现,该算法在Michael&Scott算法上进行了一些修改。

二、ConcurrentLinkedQueue的结构

通过ConcurrentLinkedQueue的类图来分析一下它的结构

image-20230504194016384

ConcurrentLinkedQueue由head节点和tail节点组成,每个节点(Node)由节点元素(item)和指向下一个节点(next)的引用组成,节点与节点之间就是通过这个next关联起来,从而组成一张链表结构的队列。默认情况下head节点存储的元素为空,tail节点等于head节点。

image-20230504194048249

三、入队列

3.1、入队列的过程

入队列就是将入队节点添加到队列的尾部。

为了方便理解入队时队列的变化,以及head节点和tail节点的变化,这里以一个示例来展开介绍。假设我们想在一个队列中依次插入4个节点,为了帮助大家理解,每添加一个节点就做了一个队列的快照图。

image-20230504194304259

  • 加元素1。队列更新head节点的next节点为元素1节点。又因为tail节点默认情况下等于head节点,所以它们的next节点都指向元素1节点。
  • 加元素2。队列首先设置元素1节点的next节点为元素2节点,然后更新tail节点指向元素2节点。
  • 加元素3,设置tail节点的next节点为元素3节点。
  • 加元素4,设置元素3的next节点为元素4节点,然后将tail节点指向元素4节点。

通过调试入队过程并观察head节点和tail节点的变化,发现入队主要做两件事情:

  • 第一是将入队节点设置成当前队列尾节点的下一个节点;
  • 第二是更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点,如果tail节点的next节点为空,则将入队节点设置成tail的next节点,所以tail节点不总是尾节点(理解这一点对于我们研究源码会非常有帮助)。

通过对上面的分析,我们从单线程入队的角度理解了入队过程,但是多个线程同时进行入队的情况就变得更加复杂了,因为可能会出现其他线程插队的情况。如果有一个线程正在入队,那么它必须先获取尾节点,然后设置尾节点的下一个节点为入队节点,但这时可能有另外一个线程插队了,那么队列的尾节点就会发生变化,这时当前线程要暂停入队操作,然后重新获取尾节点。让我们再通过源码来详细分析一下它是如何使用CAS算法来入队的。

image-20230504194714652

从源代码角度来看,整个入队过程主要做两件事情:第一是定位出尾节点;第二是使用CAS算法将入队节点设置成尾节点的next节点,如不成功则重试。

3.2、定位尾节点

tail节点并不总是尾节点,所以每次入队都必须先通过tail节点来找到尾节点。尾节点可能是tail节点,也可能是tail节点的next节点。代码中循环体中的第一个if就是判断tail是否有next节点,有则表示next节点可能是尾节点。获取tail节点的next节点需要注意的是p节点等于p的next节点的情况,只有一种可能就是p节点和p的next节点都等于空,表示这个队列刚初始化,正准备添加节点,所以需要返回head节点。获取p节点的next节点代码如下。

image-20230504195021525

3.3、设置入队节点为尾节点

p.casNext(null,n)方法用于将入队节点设置为当前队列尾节点的next节点,如果p是null,表示p是当前队列的尾节点,如果不为null,表示有其他线程更新了尾节点,则需要重新获取当前队列的尾节点。

3.4、HOPS的设计意图

上面分析过对于先进先出的队列入队所要做的事情是将入队节点设置成尾节点,doug lea写的代码和逻辑还是稍微有点复杂。那么,我用以下方式来实现是否可行?

image-20230504195155563

让tail节点永远作为队列的尾节点,这样实现代码量非常少,而且逻辑清晰和易懂。但是,这么做有个缺点,每次都需要使用循环CAS更新tail节点。

如果能减少CAS更新tail节点的次数,就能提高入队的效率,所以doug lea使用hops变量来控制并减少tail节点的更新频率,并不是每次节点入队后都将tail节点更新成尾节点,而是当tail节点和尾节点的距离大于等于常量HOPS的值(默认等于1)时才更新tail节点,tail和尾节点的距离越长,使用CAS更新tail节点的次数就会越少,但是距离越长带来的负面效果就是每次入队时定位尾节点的时间就越长,因为循环体需要多循环一次来定位出尾节点,但是这样仍然能提高入队的效率。

因为从本质上来看它通过增加对volatile变量的读操作来减少对volatile变量的写操作,而对volatile变量的写操作开销要远远大于读操作,所以入队效率会有所提升。

image-20230504195309790

四、出队列

出队列的就是从队列里返回一个节点元素,并清空该节点对元素的引用。让我们通过每个节点出队的快照来观察一下head节点的变化。

image-20230504195346029

从图中可知,并不是每次出队时都更新head节点,当head节点里有元素时,直接弹出head节点里的元素,而不会更新head节点。只有当head节点里没有元素时,出队操作才会更新head节点。

这种做法也是通过hops变量来减少使用CAS更新head节点的消耗,从而提高出队效率。让我们再通过源码来深入分析下出队过程。

image-20230504195455878

首先获取头节点的元素,然后判断头节点元素是否为空,如果为空,表示另外一个线程已经进行了一次出队操作将该节点的元素取走,如果不为空,则使用CAS的方式将头节点的引用设置成null,如果CAS成功,则直接返回头节点的元素,如果不成功,表示另外一个线程已经进行了一次出队操作更新了head节点,导致元素发生了变化,需要重新获取头节点。

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

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

相关文章

Python——基于YOLOV8的车牌识别(源码+教程)

目录 一、前言 二 、完成效果 三、 项目包 四、运行项目 (教程) 一、前言 YOLOv8LPRNet车牌定位与识别https://www.bilibili.com/video/BV1vk4y1E7MZ/ 最近做了有一个车牌识别的小需求,今天完成了,在此记录和分享 首先&#x…

linux修改程序的配置文件

修改指定文件中的数&#xff0c;例如创建一个文件如图 把6修改成7 修改完成 代码如下&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #incl…

7.设计模式之责任链模式

前言 责任链&#xff0c;即将能够处理同一类请求的对象连成一条链&#xff0c;所提交的请求沿着链传递&#xff0c; 链上的对象逐个判断是否有能力处理该请求&#xff0c;如果能则处理&#xff0c;如果不能则传递给链上的下一个对象。为了避免请求发送者与多个请求处理者耦合在…

地狱级的字节跳动面试,6年测开的我被按在地上摩擦.....

前几天我朋友跟我吐苦水&#xff0c;这波面试又把他打击到了&#xff0c;做了快6年软件测试员。。。为了进大厂&#xff0c;也花了很多时间和精力在面试准备上&#xff0c;也刷了很多题。但题刷多了之后有点怀疑人生&#xff0c;不知道刷的这些题在之后的工作中能不能用到&…

SpringBoot+myBatis(plus)+MySQL+VUE最基础简易的前后端全栈demo制作

网站全栈制作&#xff1a; 一&#xff1a;后端 为了跟公司后端更好的扯皮&#xff08;不是&#xff09;&#xff0c;本人决定学一下java语言的后端接口书写。 项目制作&#xff1a;后端采用SpringBootmyBatis(plus)mysql&#xff08;IDE为IDEA软件&#xff09;。前端采用Vue…

macOS本地python环境/vscode/导入python包/设置python解释器

查看macbook本地是否有python环境 输入python或者python3&#xff0c;退出python环境使用exit()&#xff0c;别忘了括号 没有的话去官网安装https://www.python.org/ 2. 安装vscode 官网https://code.visualstudio.com/ 3. 安装插件 点击左边的“插件”按钮&#xff0c;安装…

wangzherongyao PMO

感谢【五一节】大家的相遇&#xff0c;总结下。 2023年05月02日&#xff0c;【第一组】组队开黑 我总结了下这天为什么打的那么好&#xff0c;首先赛季初段位在王者附近&#xff0c;大家心态重视程度也高&#xff0c;不轻敌&#xff0c;也不盲目&#xff0c;运营好兵线一步一步…

【需求响应】基于进化算法的住宅光伏电池系统需求响应研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Selenium原理以及Python从零实现

Selenium简介 Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持的浏览器包括IE&#xff08;7, 8, 9, 10, 11&#xff09;&#xff0c;Mozilla Firefox&#xff0c;Safari&#xff0c;Google Chrome&a…

OpenCV教程——处理图像像素及图像掩膜

1.像素值 像素值是图像被数字化时由计算机赋予的值&#xff0c;代表了图像中某一小方块&#xff08;即【像素点】&#xff09;的平均亮度信息。 灰度图像通常用8位表示一个像素&#xff0c;这样总共有256个灰度等级&#xff08;像素值在0&#xff5e;255之间&#xff09;。 …

【VSLAM】ORB-SLAM3安装部署与运行

心口如一&#xff0c;犹不失为光明磊落丈夫之行也。——梁启超 文章目录 :smirk:1. ORB-SLAM3介绍:blush:2. 代码安装部署1. 安装ros与opencv2. 安装Pangolin作为可视化和用户界面3. 安装Eigen3一个开源线性库&#xff0c;可进行矩阵运算4. 安装ORB-SLAM3 :satisfied:3. 案例运…

架构-软件工程模块-1

概述 这一模块选择题的分值比较多&#xff0c;案例题和论文也有能用上的地方。主要知识点会特殊标注或说明。 软件开发生命周期 软件工程三要素&#xff1a;方法、工具、过程。不会直接考&#xff0c;但可帮助记忆理解。 传统软件生命周期方法学分为&#xff1a;&#xff08;选…

ChatGPT的强化学习部分介绍——PPO算法实战LunarLander-v2

PPO算法 近线策略优化算法&#xff08;Proximal Policy Optimization Algorithms&#xff09; 即属于AC框架下的算法&#xff0c;在采样策略梯度算法训练方法的同时&#xff0c;重复利用历史采样的数据进行网络参数更新&#xff0c;提升了策略梯度方法的学习效率。 PPO重要的突…

尚硅谷-宋红康-JVM上中下篇完整笔记-JVM中篇

一.Class文件结构 1.概述 1.1 字节码文件的跨平台性 所有的JVM全部遵守Java虚拟机规范:Java SE Specifications&#xff0c;也就是说所有的JV环境都是一样的&#xff0c;这样一来字节码文件可以在各种JVM上运行。 1.2 Java的前端编译器 想要让一个Java程序正确地运行在JVM中&am…

177_模型_Power BI 进销存6大日期维度期初与期末

177_模型_Power BI 进销存6大日期维度期初与期末 一、背景 在经销存报表设计中&#xff0c;经常会遇到的便是期初与期末。当然我们这里说期初与期末指的是期初库存与期末库存。 这里的期一般常见的会有&#xff1a;年月日。本案例将演示 6 大日期维度&#xff0c;分别是&…

勒索病毒“顽疾”,没有“特效药”吗?

基础设施瘫痪、企业和高校重要文件被加密、毕业论文瞬间秒没……这就是六年前的今天&#xff0c;WannaCry勒索攻击爆发时的真实场景。攻击导致150多个国家数百万台计算机受影响&#xff0c;也让勒索病毒首次被全世界广泛关注。 六年后&#xff0c;勒索攻击仍是全球最严重的网络…

Kali E:Unable to locate package错误解决

默认的新装的kali 可能都会遇到这个安装报错E: Unable to locate package httrack问题&#xff0c;今天我记录下彻底解决过程和效果。 Command httrack not found, but can be installed with: apt install httrack Do you want to install it? (N/y)y apt install httrack Re…

什么是域名流量劫持?

作为传统的互联网攻击方式&#xff0c;域名流量劫持已经十分常见&#xff0c;这种网络攻击将会在不经授权的情况下控制或重定向一个域名的DNS记录。域名劫持的影响难以估量&#xff0c;因为它可以导致在访问一个网站时&#xff0c;用户被引导到另一个不相关的网站&#xff0c;对…

深入理解java虚拟机精华总结:运行时栈帧结构、方法调用、字节码解释执行引擎

深入理解java虚拟机精华总结&#xff1a;运行时栈帧结构、方法调用、字节码解释执行引擎 运行时栈帧结构局部变量表操作数栈动态连接方法返回地址 方法调用解析分派静态分派动态分派 基于栈的字节码解释执行引擎 运行时栈帧结构 Java虚拟机以方法作为最基本的执行单元&#xf…

Vue3 自定义指令让元素自适应高度,el-table在可视区域内滚起来

我始终坚持&#xff0c;前端开发不能满足于实现功能&#xff0c;而是需要提供优秀的交互与用户体验。即使没有产品没有UI的小项目&#xff0c;也可以自己控制出品质量&#xff0c;做到小而美。所以前端们不仅仅需要了解框架如何用&#xff0c;还要学习一些设计、交互、体验的知…