当前位置: 首页 > 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&#…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

鸿蒙(HarmonyOS5)实现跳一跳小游戏

下面我将介绍如何使用鸿蒙的ArkUI框架&#xff0c;实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

WEB3全栈开发——面试专业技能点P7前端与链上集成

一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染&#xff08;SSR&#xff09;与静态网站生成&#xff08;SSG&#xff09; 框架&#xff0c;由 Vercel 开发。它简化了构建生产级 React 应用的过程&#xff0c;并内置了很多特性&#xff1a; ✅ 文件系…...