并发容器之ConcurrentLinkedQueue

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一种适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,由于ConcurrentLinkedQueue是一种非阻塞的队列,通常ConcurrentLinkedQueue性能好于BlockingQueue。是一种基于链表节点的无界线程安全队列。该队列的元素遵循先进先出FIFO的原则,该队列不允许为null

源码分析

内部的队列使用单向链表的方式实现,新元素会被插入队列末尾,出队时从队列头部获取一个元素,队列进行出队入队时对节点的操作是通过CAS实现的,保证线程安全

// 队首
private transient volatile Node<E> head;
// 队尾
private transient volatile Node<E> tail;

public ConcurrentLinkedQueue() {
   // 默认头节点、尾结点是Node中为null的哨兵节点
   // 初始时,head、tail 都指向同一个 item 为 null 的节点
    head = tail = new Node<E>(null);
}

private static class Node<E{
  // 存放节点的值
  volatile E item;
  // 存放下一个节点
  volatile Node<E> next;
// 内部的操作全部依赖于UNSAFE的CAS操作来实现原子性
  Node(E item) {
    UNSAFE.putObject(this, itemOffset, item);
  }
 // 更改Node中的数据域item
  boolean casItem(E cmp, E val) {
    return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
  }
// 更改Node中的指针域next
  void lazySetNext(Node<E> val) {
    UNSAFE.putOrderedObject(this, nextOffset, val);
  }
// 更改Node中的指针域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;
  // 偏移量
  private static final long itemOffset;
  // 下一个元素的偏移量
  private static final long nextOffset;
}
重要方法

add()和offer()都是加入元素的方法,add方法内部也是调用的offer方法

offer方法入队
public boolean offer(E e) {
  // 检查是否为null,会抛出空指针
    checkNotNull(e);
  // 构造Node节点
    final Node<E> newNode = new Node<E>(e);
// 从尾结点进行插入,循环直到成功为止
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
      // 如果q为null,说明当前p是尾结点,尝试加入到队尾,如果加入失败,表示其他线程已经修改了p的指向
        if (q == null) { // 初始时,head、tail 都指向同一个 item 为 null 的节点
            // 使用CAS操作设置p节点的next节点,但是没有更新尾结点
          // 如果有多线程操作,会导致第一次CAS操作失败,再次执行for循环
            if (p.casNext(null, newNode)) { // CAS操作成功,新增节点被放入到链表中
                
               // p!=t,表示有多线程操作,导致第一次cas操作没有成功,此次不是第一次cas操作,此时在进行设置尾结点
                if (p != t) // hop two nodes at a time
                  // 设置当前尾结点为新插入的节点
                    casTail(t, newNode);  // Failure is OK.
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        else if (p == q) // 多线程操作时,由于poll操作移除元素后可能会把head变成自引用(环形链表),此时head的next节点也是head,所以需要重新找到新的head
            // We have fallen off list.  If tail is unchanged, it
            // will also be off-list, in which case we need to
            // jump to head, from which all live nodes are always
            // reachable.  Else the new tail is a better bet.
          
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
          // 寻找尾结点,找到当前的尾结点之后,再次执行for循环
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

poll()和peek()都是取头元素节点,前者会删除元素,后者不会删除元素

poll方法出队
public E poll() {
    restartFromHead:
    for (;;) {
      // 从头节点开始遍历
        for (Node<E> h = head, p = h, q;;) {
          // 保存当前节点
            E item = p.item;
     // 当前节点有值,并且使用CAS操作将当前节点变为null成功
            if (item != null && p.casItem(item, null)) {
                // Successful CAS is the linearization point
                // for item to be removed from this queue.
              // CAS操作成功,则标记当前节点从链表中移除
              // 只有多线程操作时,使得第一次p!=h时才会设置头节点为p
                if (p != h) // hop two nodes at a time
                    updateHead(h, ((q = p.next) != null) ? q : p);
                return item;
            }
          // 当前队列为空 
            else if ((q = p.next) == null) {
                updateHead(h, p);
                return null;
            }
          // 多线程同时操作时才会出现该情况,当前节点自引用,需要重新寻找新的队列头节点
            else if (p == q)
                continue restartFromHead;
            else // 多线程操作时,会导致第一次判断时item为null,且此时已经有了新插入的节点了,需要重新指定头节点
                p = q;
        }
    }
}
peek方法
// 与poll方法类似,只是少了cas操作来清空头节点的值
public E peek() {
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;
            if (item != null || (q = p.next) == null) {
                updateHead(h, p);
                return item;
            }
            else if (p == q)
                continue restartFromHead;
            else
                p = q;
        }
    }
}
remove方法

如果队列中存在该元素,则删除该元素

public boolean remove(Object o) {
    if (o != null) {
        Node<E> next, pred = null;
        for (Node<E> p = first(); p != null; pred = p, p = next) {
            boolean removed = false;
            E item = p.item;
            if (item != null) {
                if (!o.equals(item)) {
                    next = succ(p);
                    continue;
                }
              // 使用cas操作来进行remove
                removed = p.casItem(item, null);
            }

            next = succ(p);
          // 如果有前驱节点,并且next不为空,则需要将这两个连接起来
            if (pred != null && next != null// unlink
                pred.casNext(p, next);
            if (removed)
                return true;
        }
    }
    return false;
}

head和tail的更新时机

tail 更新时机:tail 节点不总是尾节点,如果 tail 节点的 next 节点不为空,则将入队节点设置成 tail 节点;如果 tail 节点的 next 节点为空,则只入队不更新尾节点。

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

head 和 tail 的更新总是间隔了一个,是为了减少CAS的更新操作,如果大量的入队操作,每次都要执行 CAS 进行 tail 的更新,汇总起来对性能也会是大大的损耗。如果能减少 CAS 更新的操作,无疑可以大大提升入队的操作效率

https://zhhll.icu/2021/多线程/并发容器/1.ConcurrentLinkedQueue/

本文由 mdnice 多平台发布

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

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

相关文章

uniapp自定义导航栏左中右内容和图标,以及点击事件

uniapp自定义导航栏左中右内容和图标&#xff0c;以及点击事件 效果&#xff1a; 页面&#xff1a; <view class"navigation-bar"><view class"navigation-bar-left" click"navigateBack"><u-icon name"arrow-left"…

【嵌入式开发 Linux 常用命令系列 4.3 -- git add 时单独排除某个目录或者文件】

文章目录 git add 时单独排除某个目录或者文件使用 .gitignore 文件使用命令行排除文件或目录 git add 时单独排除某个目录或者文件 在使用 git add 命令时&#xff0c;如果你想要排除特定的目录或文件&#xff0c;可以使用 .gitignore 文件或使用路径规范来指定不想添加的文件…

ES数据同步:定时重启Logstash,并清除相关数据。

文章目录 引言I crontab 周期自动执行相应命令1.1 crontab语法1.2 crontab 文件示例1.3 系统范围内的 crontab 文件II 定期重启Logstash2.1 同步当天日期的数据2.2 同步指定日期的数据III logstash3.1 conf 参考3.2 statement_filepath3.3 执行IV 预备知识

新的变速箱滚动轴承和齿轮故障数据

变速箱是传动系统中非常关键的一部分&#xff0c;它由齿轮、传动轴、轴承和壳体等组成。变速箱的主要功用包括&#xff1a;&#xff08;1&#xff09;能够改变传动比&#xff0c;按实际情况调整驱动轮转矩和转速&#xff0c;进而满足复杂的行车要求&#xff1b;&#xff08;2&a…

机器学习金融应用技术指南

1 范围 本文件提供了金融业开展机器学习应用涉及的体系框架、计算资源、数据资源、机器学习引擎、机 器学习服务、安全管理、内控管理等方面的建议。 本文件适用于开展机器学习金融应用的金融机构、技术服务商、第三方安全评估机构等。 2 规范性引用文件 下列文件中的内容通过…

新型储能是什么,储能系统解决方案现状及趋势详细说明

新型储能是指新兴的能够存储电能并在需要时释放的储能技术。其中主要包括光伏储能和商业储能。 光伏储能是指通过光伏电池将太阳能转化为电能&#xff0c;并将其存储起来以供后续使用。光伏储能系统一般由太阳能电池板、储能装置和逆变器组成。光伏储能可以将白天产生的电能存…

2024年华为OD机试真题-任务处理-Python-OD统一考试(C卷)

题目描述&#xff1a; 在某个项目中有多个任务&#xff08;用 tasks 数组表示&#xff09;需要您进行处理&#xff0c;其中 tasks[i] [si, ei]&#xff0c;你可以在 si < day < ei 中的任意一天处理该任务。请返回你可以处理的最大任务数。 注&#xff1a;一天可以完成一…

关于linux中mysql8.0修改lower_case_table_names

为什么要修改 在linux中安装mysql时&#xff0c;默认的lower_case_table_names0&#xff0c;即对大小写不敏感在windwos中的mysql&#xff0c;默认的lower_case_table_names1在做程序迁移时&#xff0c;导致linux中部署的服务器无法访问打mysql中的表 修改中的问题 mysql8.0…

代码随想录算法训练营 Day27 回溯算法3

39. 组合总和 思路 根据题意画二叉树 这道题与之前的组合总和的区别在于&#xff0c;数组中的数字可以多次使用&#xff0c;因此每次递归时的startIndex依旧是从当前的i开始 尝试写代码&#xff1a; class Solution:def __init__(self):self.path []self.result []def co…

Java练习题目3:输入一个学生的5门课成绩及对应的学分,计算该同学的加权平均分(WeightedAverageScore3)

每日小语 我们没有意识到惯用语言的结构有多大的力量。可以说&#xff0c;它通过语义反应机制奴役我们。 ——阿尔弗雷德科日布斯基 思考 输入5门课成绩&#xff0c;学分&#xff0c;加权平均分公式 [&#xff08;课程A成绩*课程A学分&#xff09;&#xff08;课程成绩B*课程…

Mybatis-Plus实现乐观锁

1 悲观锁和乐观锁场景和介绍 乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!! 1.1 悲观锁&#xff1a; 悲观锁的基本思想是&#xff0c;在整个数据访问过程中&#xff0c;将共享资源锁定&#xff0c;以确保其他线程或进程不能同时访问和修改该…

Learn OpenGL 19 几何着色器

几何着色器 在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader)&#xff0c;几何着色器的输入是一个图元&#xff08;如点或三角形&#xff09;的一组顶点。几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换。然而&#xff0c;几何着色器最有趣的地方…

动态路由协议——OSPF

目录 一.OSPF来源 二.OSPF术语 1.area id——区域的划分 2.cost——路径开销值 3.route id 4.LSDB表 5.邻居表 6.OSPF路由表 三.OSPF工作过程 1.交互hello报文建立邻居关系 2.选举主从 3.交互LSDB摘要信息 4.LSR,LSU,LSACK同步LSDB表项 5.各自计算路由 四.OSPF交…

简单函数_学分绩点

任务描述 北京大学对本科生的成绩施行平均学分绩点制&#xff08;GPA&#xff09;。既将学生的实际考分根据不同的学科的不同学分按一定的公式进行计算。 公式如下&#xff1a; 实际成绩 绩点 90——100 4.0 85——89 3.7 82——84 3.3 78——81 3.0 75——77 …

redis【面试题】

目录 Java全技术栈面试题合集地址Redis篇1.Redis 的数据类型&#xff1f;2.Redis 是单进程单线程的&#xff1f;3.一个字符串类型的值能存储最大容量是多少&#xff1f;4.Redis 的持久化机制是什么&#xff1f;各自的优缺点&#xff1f;5.redis 过期键的删除策略&#xff1f;6.…

maven手动上传的第三方包 打包项目报错 Could not find xxx in central 解决办法

背景: 在Maven私服手动上传了第三方的jar包, 只有jar包, 没有pom文件, 项目在ide中可以正常编译启动,但打包报错无法找到jar包 解决办法: 上传jar包的时候, 点击生成pom. 则打包的时候不会报错

Python判断一个数是否为素数

在Python中&#xff0c;你可以编写一个函数来判断一个数是否为素数。素数是指只有1和它本身两个正因数&#xff08;不包括1本身&#xff09;的自然数。以下是一个简单的示例代码&#xff1a; python复制代码 def is_prime(n): if n < 1: return False if n < 3: return …

pyrealsense2获取保存点云

一、第一种实现代码 Python import sys import cv2 import pyrealsense2 as rs import numpy as np import keyboard import open3d as o3d import osif __name__ "__main__":output_folder output_data/os.makedirs(output_folder, exist_okTrue)pipeline rs.p…

三级数据库技术知识点(详解!!!)

1、从功能角度数据库应用系统可以划分为表示层、业务逻辑层、数据访问层、数据持久层四个层次&#xff0c;其中负责向表示层直接传送数据的是业务逻辑层。 【解析】表示层负责所有与用户交互的功能;业务逻辑层负责根据业务逻辑需要将表示层获取的数据进行组织后&#xff0c;传…

怎样保持SSH长时连接不断开(客户机)

怎样保持SSH连接不自动断开? 一、前言 远程访问服务器的时候&#xff0c;长时间不操作就会断开连接&#xff0c;这让我苦恼不已&#xff0c;因此花了不少时间折腾&#xff0c;因为我用过的很多方法都无效&#xff0c;经过几番测试&#xff0c;找到了一种解决方案。 不过我只…