当前位置: 首页 > news >正文

并发变成实战-原子变量与非阻塞同步机制

文章目录

  • 1.锁的劣势
  • 2.硬件对并发的支持
    • 2.1 比较并交换
    • 2.2 非阻塞的计数器
    • 3.原子变量类
    • 3.1 原子变量是一种“更好的volatile”
    • 3.2 性能比较:锁与原子变量
    • 4.非阻塞算法
    • 4.1 非阻塞的栈
    • 4.2 非阻塞的链表
    • 4.3 ABA问题

非阻塞算法设计和实现上要复杂的多,但在可伸缩性和活跃性上拥有巨大的优势。
原子变量提供了与volatile变量相同的语义,此外还支持原子的更新操作,从而更适用于实现计数器、序列发生器和统计数据收集等。

1.锁的劣势

多个线程请求锁时,一些线程将被挂起并且在稍后恢复运行。当线程恢复执行时,必须等待其他线程执行完他们的时间片后才能被调度执行。挂起和恢复线程等过程存在很大的开销,并且通常存在较长时间的中断。
volatile更轻量级的同步机制,不会发生上下文切换或线程调度等操作。局限:虽然提供了可见性,但不能用于构建原子的复合操作。因此当一个变量依赖其它变量或新值依赖于旧值时,就不能用volatile(例如,++i 包含了3个独立的操作–获取变量当前值,加1,写入新值,整个过程必须是原子的)。
锁定时,当一个线程等待锁时,它不能做其它的事情。如果一个线程在持有锁时被延迟执行(如缺页错误、调度延迟等),所有需要这个锁的其它线程都无法执行。如果持有锁的线程永久阻塞,那么程序将永远无法继续执行。

2.硬件对并发的支持

独占锁是一种悲观锁–假设最坏的情况
对于细粒度的操作,有一种乐观的方法,可以不发生干扰的情况下完成更新操作。这种方法需要借助冲突检查机制来判断在更新过程中是否存在来自其他线程的干扰,如果存在,这个操作将失败,并且可以重试(CAS等)。

2.1 比较并交换

CAS包含了3个操作数–需要读写的内存位置V、进行比较的值A和拟写入的新值B。当且仅当V的值等于A时,CAS才会用原子方式用新值B来更新V的值,否则不会执行任何操作。无论位置V的值是否等于A,都将返回V原有的值。
CAS含义:我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少。
CAS是一项乐观的技术。如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。

/*** 模拟CAS操作*/
public class SimulatedCAS {private int value;public synchronized int get() {return value;}public synchronized int compareAndSwap (int expectedValue,int newValue) {int oldValue = value;if (oldValue == expectedValue) {value = newValue;}return oldValue;}public synchronized boolean compareAndSet(int expectedValue,int newValue) {return (expectedValue == compareAndSwap(expectedValue,newValue));}
}

多个线程尝试更新同一个变量时,只有一个线程能更新变量的值,而其他线程都将失败。失败的线程不会被挂起,而是会再次尝试。由于线程竞争失败后不会阻塞,因此他可以决定是否重新尝试,或者执行一些恢复操作,或者不执行任何操作。

2.2 非阻塞的计数器

使用CAS实现一个简单的非阻塞计数器

public class CasCounter {private SimulatedCAS value;public int getValue() {return value.get();}public int increment() {int v;do {v = value.get();} while (v != value.compareAndSwap(v,v + 1)) {return v + 1;}}
}

通常,反复的重试是一种合理的策略,但一些竞争激烈的情况下,更好的方式是在重试之前首先等待一段时间或者回退,从而避免造成活锁问题。
CasCounter 不会阻塞,如果其他线程同时更新计数器,那么会多次执行重试操作。(如果仅需要一个计数器或序列生成器,那么可以直接使用AtomicInteger或AtomicLong,他们能提供原子的递增方法)
竞争程度不高时,基于CAS的计数器在性能上远胜于基于锁的计数器。

3.原子变量类

原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger表示一个int类型的值,并提供了get和set方法。发生竞争时有很好的可伸缩性。
共12个原子变量类,分为4组:标量类,更新器类,数组类以及复合变量类。
最常用的原子变量就是标量类:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。他们都支持CAS。
原子数组类(只支持Integer、Long、Reference版本)中的元素可以实现原子更新。原子数组类为数组的元素提供了volatile类型的访问语义,这是普通数组不具备的特性–volatile类型的数组仅在数组引用上具有volatile,在其元素上没有。
原子的标量类扩展了Number类,但并没有扩展一些基本类型的包装类,如Integer或Long,因为基本类型的包装类是不可修改的,而原子变量类是可修改的。

3.1 原子变量是一种“更好的volatile”

public class CasNumberRange {private static class IntPair {//不变形条件: lower <= upperfinal int lower;final int upper;private IntPair(int lower, int upper) {this.lower = lower;this.upper = upper;}}//使用AtomicReference和IntPair来保存状态private final AtomicReference<IntPair> values = new AtomicReference<>(new IntPair(0, 0));public int getLower() {return values.get().lower;}public int getUpper() {return values.get().upper;}public void setLower(int i) {while (true) {IntPair oldv = values.get();if (i > oldv.upper) {throw new IllegalArgumentException("Can not set lower to " + i + " > upper");}IntPair newv = new IntPair(i, oldv.upper);//CAS 避免竞态if (values.compareAndSet(oldv, newv)) {return;}}}
}

3.2 性能比较:锁与原子变量

低竞争情况下,原子变量的性能高于锁,高竞争情况下,锁性能高于原子变量
但实际情况中,原子变量在可伸缩性上要高于锁,因为在应对常见的竞争程度时,原子变量的效率会更高。

4.非阻塞算法

4.1 非阻塞的栈

非阻塞算法通常比基于锁的算法更复杂。算法关键在于,找出如何将原子修改的范围缩小到单个变量上,同时还要维护数据一致性。
栈时最简单的链式数据结构, 每个元素只指向一个元素

public class ConcurrentStack<E> {AtomicReference<Node<E>> top = new AtomicReference<>();public void push(E item) {Node<E> newHead = new Node<>(item);Node<E> oldHead;do {oldHead = top.get();//新节点的next指向当前栈顶newHead.next = oldHead;//使用CAS把新节点放到栈顶//如果开始插入节点前,栈顶没有发生变化,CAS就会成功更新栈顶//如果栈顶发生变化(被其他线程修改),CAS会失败,并根据新的栈状态来更新节点} while (top.compareAndSet(oldHead,newHead));}private static class Node<E> {public final E item;public Node<E> next;public Node(E item) {this.item = item;}}
}

非阻塞算法特性:某工作的完成具有不确定性,必须重新执行。
CAS既能提供原子性,又能提供可见性,所以非阻塞算法是线程安全的。

4.2 非阻塞的链表

链表需要单独维护头指针和尾指针来快速访问。有两个指针指向位于尾部的节点:当前最后一个元素的next指针,以及尾节点。当插入一个元素时,这两个指针都需要采用原子操作来更新。
技巧

  • 即使在一个包含多个步骤的更新操作中,也要确保数据结构总是处于一致的状态。B线程到达时,如果A正在执行更新操作,B不能立即开始自己的更新操作。然后B可以等待(通过反复检查队列的状态)并直到A完成
  • 如果B到达时发现A正在修改数据结构,那么在数据结构中应该有足够多的信息,使得B能完成A的更新操作。如果B帮助A完成了更新操作,那么B可以执行自己的操作,而不用等A操作完成。当A恢复后视图完成其操作时,会发现B已经替他完成了。
    空队列通常包含一个“哨兵节点(Sentinel)”或者“哑节点(Dummy)”,并且头结点和尾结点在初始化时都指向该哨兵节点。尾节点通常要么指向哨兵节点(如果队列为空),即队列的最后一个元素,要么(当有操作正在执行更新操作时)指向倒数第二个元素。
public class LinkedQueue<E> {private static class Node<E> {final E item;final AtomicReference<Node<E>> next;private Node(E item, AtomicReference<Node<E>> next) {this.item = item;this.next = next;}}private final Node<E> dummy = new Node<>(null,null);private final AtomicReference<Node<E>> head = new AtomicReference<>(dummy);private final AtomicReference<Node<E>> tail = new AtomicReference<>(dummy);//插入元素需要更新两个指针public boolean put(E item) {Node<E> newNode = new Node<>(item, null);while (true) {Node<E> curTail = tail.get();Node<E> tailNext = curTail.next.get();if (curTail == tail.get()) {if (tailNext != null) {//队列处于中间状态,推进尾结点tail.compareAndSet(curTail,tailNext);} else {//处于稳定状态,尝试插入新节点if (curTail.next.compareAndSet(null,newNode)) {//插入操作成功,尝试推进尾结点tail.compareAndSet(curTail,newNode);return true;}}}}}
}

插入元素需要更新两个指针。首先更新当前最后一个元素的next指针,将新节点链接到队尾,然后更新尾结点,将其指向这个新的元素。
当队列处于稳定状态,尾结点的next域降为空,如果队列处于中间状态,那么tail.next将为非空。因此任何线程都能通过检查tail.next来获取队列当前的状态。

4.3 ABA问题

CAS判断值相等的间隙,值是否发生过改变
解决方法:改变引用值的时候,增加一个版本号。每次改变的操作都更新版本号。

相关文章:

并发变成实战-原子变量与非阻塞同步机制

文章目录1.锁的劣势2.硬件对并发的支持2.1 比较并交换2.2 非阻塞的计数器3.原子变量类3.1 原子变量是一种“更好的volatile”3.2 性能比较&#xff1a;锁与原子变量4.非阻塞算法4.1 非阻塞的栈4.2 非阻塞的链表4.3 ABA问题非阻塞算法设计和实现上要复杂的多&#xff0c;但在可伸…...

sql数据库常用操作指令

一、操作库-- 创建库create database db1;-- 创建库是否存在&#xff0c;不存在则创建create database if not exists db1;-- 查看所有数据库show databases;-- 查看某个数据库的定义信息 show create database db1; -- 修改数据库字符信息alter database db1 character set ut…...

4-1 定时任务的示例10个

文章目录前言基本命令与格式示例前言 Linux crontab 是用来定期执行程序的命令。当安装完成操作系统之后&#xff0c;默认都已经安装&#xff0c;并启动此任务调度命令。 crond 命令每分钟会定期检查是否有要执行的工作&#xff0c;如果有要执行的工作便会自动执行该工作。 基…...

外贸建站多少钱才能达到预期效果?

外贸建站多少钱才能达到预期效果&#xff1f;这是每个外贸企业都会问的问题。作为一个做外贸建站多年的人&#xff0c;我有一些个人的操盘感想。 首先&#xff0c;我认为外贸建站的投资是非常必要的。 因为在现代社会&#xff0c;网站已经成为外贸企业开展业务的必要工具之一…...

【Java学习笔记】5.Java 基本数据类型

Java 基本数据类型 变量就是申请内存来存储值。也就是说&#xff0c;当创建变量的时候&#xff0c;需要在内存中申请空间。 内存管理系统根据变量的类型为变量分配存储空间&#xff0c;分配的空间只能用来储存该类型数据。 因此&#xff0c;通过定义不同类型的变量&#xf…...

InnoDB 死锁和问题排查

文章目录死锁&#xff08;dead lock&#xff09;示例 1问题排查查看连接的线程查看相关的表查看最近一次的死锁信息查看服务器的锁信息查看正在使用的表如何尽可能地避免死锁死锁&#xff08;dead lock&#xff09; 两个及以上的事务各自持有对方需要的锁&#xff0c;导致双方…...

tensorflow07——使用tf.keras搭建神经网络(Sequential顺序神经网络)——六步法——鸢尾花数据集分类

使用tf.keras搭建顺序神经网络 六步法——鸢尾花数据集分类 01 导入相关包 02 导入数据集&#xff0c;打乱顺序 03 建立Sequential模型 04 编译——确定优化器&#xff0c;损失函数&#xff0c;评测指标&#xff08;用哪一种准确率&#xff09; 05 训练模型——把各项参入填入…...

关于Java连接Hive,Spark等服务的Kerberos工具类封装

关于Java连接Hive&#xff0c;Spark等服务的Kerberos工具类封装 idea连接服务器的hive等相关服务的kerberos认证注意事项 idea 本地配置&#xff0c;连接服务器&#xff1b;进行kerberos认证&#xff0c;连接hive、HDFS、Spark等服务注意事项&#xff1a; 本地idea连接Hadoo…...

大数据框架之Hadoop:MapReduce(五)Yarn资源调度器

Apache YARN (Yet Another Resource Negotiator) 是 hadoop 2.0 引入的集群资源管理系统。用户可以将各种服务框架部署在 YARN 上&#xff0c;由 YARN 进行统一地管理和资源分配。 简言之&#xff0c;Yarn是一个资源调度平台&#xff0c;负责为运算程序提供服务器运算资源&…...

uniapp实现地图点聚合功能

前言 在工作中接到的一个任务&#xff0c;在app端实现如下功能&#xff1a; 地图点聚合地图页面支持tab切换&#xff08;设备、劳务、人员&#xff09;支持人员搜索显示分布 但是uniapp原有的map标签不支持点聚合功能&#xff08;最新的版本支持了点聚合功能&#xff09;&am…...

经典分类模型回顾2—GoogleNet实现图像分类(matlab版)

GoogleNet是深度学习领域的一种经典的卷积神经网络&#xff0c;其在ImageNet图像分类任务上的表现十分优秀。下面是使用Matlab实现GoogleNet的图像分类示例。 1. 数据准备 在开始之前&#xff0c;需要准备一些图像数据用来训练和测试模型&#xff0c;可以从ImageNet等数据集中…...

Java经典面试题——谈谈 final、finally、finalize 有什么不同?

典型回答 final 可以用来修饰类、方法、变量&#xff0c;分别有不同的意义&#xff0c;final 修饰的 class 代表不可以继承扩展&#xff0c; final 的变量是不可以修改的&#xff0c;而 final 的方法也是不可以重写的&#xff08;override&#xff09;。 finally 则是 Java 保…...

C#的Version类型值与SQL Server中二进制binary类型转换

使用C#语言编写的应用程序可以通过.NET Framework框架提供的Version类来控制每次发布的版本号&#xff0c;以便更好控制每次版本更新迭代。 版本号由两到四个组件组成&#xff1a;主要、次要、内部版本和修订。 版本号的格式如下所示&#xff0c; 可选组件显示在方括号 ([ 和…...

软测入门(五)接口测试Postman

Postman 一款Http接口收工测试工具。如果做自动化测试会使用jemter做。 安装 去官网下载即可。 https://www.postman.com/downloads/?utm_sourcepostman-home 功能介绍 页面上的单词基本上都能了解&#xff0c;不多介绍。 转代码&注释 可将接口的访问转为其他语言的…...

UWB通道选择、信号阻挡和反射对UWB定位范围和定位精度的影响

&#xff08;一&#xff09;介绍检查NLOS操作时需要考虑三个方面&#xff1a;&#xff08;1&#xff09;由于整体信号衰减&#xff0c;通信范围减小。&#xff08;2&#xff09;由于直接路径信号的衰减&#xff0c;导致直接路径检测范围的减小。&#xff08;3&#xff09;由于阻…...

linux基本功之列之wget命令实战

文章目录前言一. wget命令介绍二. 语法格式及常用选项三. 参考案例3.1 下载单个文件3.2 使用wget -o 下载文件并改名3.3 -c 参数&#xff0c;下载断开链接时&#xff0c;可以恢复下载3.4 wget后台下载3.5 使用wget下载整个网站四. 补充与汇总常见用法总结前言 大家好&#xff…...

学习ROS时针对gazebo相关的问题(重装与卸载是永远的神)

ResourceNotFound:gazebo_ros 错误解决 参考:https://blog.csdn.net/weixin_42591529/article/details/123869969 当将机器人加载到gazebo时,运行launch文件出现如下错误 这是由于缺少gazebo包所导致的。 解决办法:...

几个C语言容易忽略的问题

1 取模符号自增问题 我们不妨尝试写这样的程序 #include<stdio.h> int main(){int n,t5;printf("%d\n",7%(-3));//1printf("%d\n",(-7)%3);//-1while(--t)printf("%d\n",t);t5;while(t--)printf("%d\n",t);return 0; } 运行…...

CentOS 7.9安装Zabbix 4.4《保姆级教程》

CentOS 7.9安装Zabbix 4.4一、配置一览二、环境准备设置Selinux和firewalld设置软件源1.配置ustc CentOS-Base源2.安装zabbix 4.4官方源3.安装并更换epel源4.清除并生成缓存三、安装并配置Zabbix Server安装zabbix组件安装php安装mariadb并创建数据库修改zabbix_server.conf设置…...

路由器与交换机的区别(基础知识)

文章目录交换机路由器路由器和交换机的区别&#xff08;1&#xff09;工作层次不同&#xff08;2&#xff09;数据转发所依据的对象不同&#xff08;3&#xff09;传统的交换机只能分割冲突域&#xff0c;不能分割广播域&#xff1b;而路由器可以分割广播域&#xff08;4&#…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...