CASAtomic原子操作详解
什么是原子操作?如何实现原子操作?
我们在接触到事务的时候,了解到事务的一大特性是原子性,一个事务要么全部执行、要么全部不执行。
并发里的原子性和事务里的原子性有一样的内涵和概念。假定有2个操作A和B都包含多个步骤,从线程A的角度看,线程B要么全部执行要么全部不执行,B看A也是如此,那么A和B对彼此来说都是原子的。
实现原子操作可以使用锁,锁机制,满足基本需求是没有问题的,但有时候我们需要更有效,更加灵活的机制。
synchronized 关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候, 访问同一资源的其它线程需要等待,直到该线程释放锁。
这里会有些问题: 首先,如果被阻塞的线程优先级很高很重要怎么办?其次, 如果获得锁的线程一直不释放锁怎么办? 同时,还有可能出现一些例如死锁之类 的情况, 最后, 其实锁机制是一种比较粗糙, 粒度比较大的机制, 相对于像计数 器这样的需求有点儿过于笨重。为了解决这个问题,Java 提供了 Atomic 系列的 原子操作类。
这些原子操作类其实是使用当前的处理器基本都支持 CAS 的指令,比如 Intel 的汇编指令
cmpxchg,每个厂家所实现的具体算法并不一样,但是原理基本一样。 每一个 CAS 操作过程都包含三个运算符: 一个内存地址 V,一个期望的值 A 和一 个新值 B,操作的时候如果这个地址上存放的值等于这个期望的值 A,则将地址 上的值赋为新值 B,否则不做任何操作。
CAS 的基本思路就是, 如果这个地址上的值和期望的值相等, 则给其赋予新值, 否则不做任何事儿,但是要返回原值是多少。 自然 CAS 操作执行完成时, 在 业务上不一定完成了, 这个时候我们就会对 CAS 操作进行反复重试, 于是就有了 循环CAS。很明显, 循环CAS就是在一个循环里不断的做cas操作, 直到成功为 止。 Java 中的 Atomic 系列的原子操作类的实现则是利用了循环CAS来实现。
CAS 实现原子操作的三大问题
ABA 问题。
即值从A设置为B,再由B设置到A,这整个过程CAS是不知道的,还以为A从来没改过。
解决该问题的方法是使用版本号。即A->B->A变成了1A->2B->3A。
循环时间长开销大。
自旋CAS如果长时间不成功,会大量的消耗CPU资源
只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时, 我们可以使用循环 CAS 的方式来保证原子操 作, 但是对多个共享变量操作时, 循环 CAS 就无法保证操作的原子性, 这个时候 就可以用锁。
Jdk 中相关原子操作类的使用
AtomicInteger
- int AddAndGet(int delta):可以将输入的值delta与源值相加,然后将相加的值返回
- boolean compareAndSet(int expect ,int update):如果数据的值等于预期值expect,则用update更新原值
- int getAndIncrement():以原子的方式+1,注意,此处返回的值是自增前的值
- int getAndSet(int newValue):以原子的方式设置为新值,并返回旧值
AtomicIntegerArray
主要是提供原子的方式更新数组里的整型,其常用方法如下。
- int addAndGet(int i ,int delta):以原子方式将输入值与数组中索引i的元素相加,并返回修改后的值
- boolean compareAndSet(int i ,int expect ,int update):如果当前值等于 预期值,则以原子方式将数组位置i的元素设置成 update 值。
需要注意的是, 数组 value 通过构造方法传递进去, 然后 AtomicIntegerArray 会将当前数组复制一份,所以当 AtomicIntegerArray 对内部的数组元素进行修改 时,不会影响传入的数组。
更新引用类型
原子更新基本类型的 AtomicInteger,只能更新一个变量, 如果要原子更新多 个变量,就需要使用这个原子更新引用类型提供的类。 Atomic 包提供了以下 3 个类。
AtomicReference
原子更新引用类型。
如下例
public class UseAtomicReference {static AtomicReference<UserInfo> atomicUserRef;public static void main(String[] args) {UserInfo user = new UserInfo("Mark", 15);//要修改的实体的实例atomicUserRef = new AtomicReference(user);UserInfo updateUser = new UserInfo("Bill",17);atomicUserRef.compareAndSet(user,updateUser);System.out.println(atomicUserRef.get());System.out.println(user);}//定义一个实体类static class UserInfo {private volatile String name;private int age;public UserInfo(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}@Overridepublic String toString() {return super.toString() + "UserInfo{" +"name='" + name + '\'' +", age=" + age +'}';}}}
AtomicStampedReference
利用版本戳的形式记录了每次改变以后的版本号, 这样的话就不会存在 ABA 问题了。这就是AtomicStampedReference 的解决方案。AtomicMarkableReference 跟 AtomicStampedReference 差不多,AtomicStampedReference是使用 pair 的 int stamp 作为计数器使用,AtomicMarkableReference 的 pair使用的是 boolean mark。 还是那个水的例子,AtomicStampedReference 可能关心的是动过几次,AtomicMarkableReference 关心的是有没有被人动过,方法都比较简单。
public class UseAtomicStampedReference {static AtomicStampedReference<String> asr= new AtomicStampedReference("mark",0);public static void main(String[] args) throws InterruptedException {//拿到当前的版本号(旧)final int oldStamp = asr.getStamp();final String oldReference = asr.getReference();System.out.println(oldReference+"============"+oldStamp);Thread rightStampThread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+ "A:" + ":当前变量值:"+oldReference + "-当前版本戳:" + oldStamp + "-"+ asr.compareAndSet(oldReference,oldReference + "+Java", oldStamp,oldStamp + 1));}});Thread errorStampThread = new Thread(new Runnable() {@Overridepublic void run() {String reference = asr.getReference();System.out.println(Thread.currentThread().getName() + "B:"+":当前变量值:"+reference + "-当前版本戳:" + asr.getStamp() + "-"+ asr.compareAndSet(reference,reference + "+C", oldStamp,oldStamp + 1));}});rightStampThread.start();rightStampThread.join();errorStampThread.start();errorStampThread.join();System.out.println(asr.getReference()+"============"+asr.getStamp());}
}
AtomicMarkableReference
原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引 用类型。构造方法是AtomicMarkableReference(V initialRef,boolean initialMark)。
原子更新字段类
如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类, Atomic 包提供了以下3个类进行原子字段更新。
要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类, 每次使用的时候必须使用静态方法 newUpdater()创建一个更新器, 并且需要设置 想要更新的类和属性。第二步,更新类的字段(属性)必须使用 public volatile 修饰符。
AtomicIntegerFieldUpdater:
原子更新整型的字段的更新器。
AtomicLongFieldUpdater:
原子更新长整型字段的更新器。
AtomicReferenceFieldUpdater:
原子更新引用类型里的字段。
LongAdder
JDK1.8 时,java.uti l.concurrent.atomic 包中提供了一个新的原子类:LongAdder。 根据 Oracle官方文档的介绍, LongAdder在高并发的场景下会比它的前辈——AtomicLong 具有更好的性能,代价是消耗更多的内存空间。
AtomicLong是利用了底层的CAS操作来提供并发性的, 调用了Unsafe类的getAndAddLong方法, 该方法是个native方法, 它的逻辑是采用自旋的方式不断更新目标值,直到更新成功。
在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。但 是,高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时 AtomicLong 的自旋会成为瓶颈。
这就是LongAdder引入的初衷——解决高并发环境下AtomicLong的自旋瓶颈问题。
AtomicLong中有个内部变量value保存着实际的 long 值,有volatile修饰,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。
LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
LongAdder提供的api和AtomicLong比较接近,两者都能以原子的方式对long进行增减。但AtomicLong提供的接口更丰富,尤其是addAndGet、 decrementAndGet、compareAndSet 这些方法。addAndGet、 decrementAndGet提供了先增减再获得增减后的值的功能,而LongAdder则需要做同步控制才能精确获取增减后的值。如果需求需要精确的控制计数,做计数比较,AtomicLong更合适,另外,从空间方面考虑,LongAdder其实是一种“空间换时间”的思想,从这一点来讲AtomicLong更适合。
在低并发、一般的业务场景下AtomicLong是足够了。如果并发量很多,存在大量写多读少的情况,用LongAdder更合适。如果出现了是使用AtomicLong还是LongAdder的场景,需要对两种方案进行性能测试,以准确评估当前场景下的性能。
abstract class Striped64 extends Number {...transient volatile Cell[] cells;/*** Base value, used mainly when there is no contention, but also as* a fallback during table initialization races. Updated via CAS.*/transient volatile long base;...public long sum() {Cell[] as = cells; Cell a;long sum = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;}public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) {boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))longAccumulate(x, null, uncontended);}}...
}
LongAdder继承了Striped64,Striped64定义了base存基础值,Cell[]数组:竞态条件下,累加各个线程自己的槽Cell[i]中。最终累计的结果的是sum方法

而LongAdder最终结果的求和,并没有使用全局锁,返回值不是绝对准确的,因为调用这个方法时还有其他线程可能正在进行计数累加,所以只能得到某个时 刻的近似值,这也就是LongAdder并不能完全替代LongAtomic的原因之一。而且从测试情况来看,线程数越多,并发操作数越大,LongAdder的优势越 大,线程数较小时,AtomicLong 的性能还超过了 LongAdder。
其他新增
除了LongAdder,还有其他三个类LongAccumulator、DoubleAdder、
DoubleAccumulator。
LongAccumulator是LongAdder的增强版。LongAdder只能针对数值的进行加减运算,而LongAccumulator提供了自定义的函数操作。通过LongBinaryOperator,可以自定义对入参的任意操作,并返回结果(LongBinaryOperator接收2个long作为参数,并返回1个 long)。
LongAccumulator 内部原理和 LongAdder 几乎完全一样。
DoubleAdder和DoubleAccumulator用于操作double原始类型。
相关文章:
CASAtomic原子操作详解
什么是原子操作?如何实现原子操作? 我们在接触到事务的时候,了解到事务的一大特性是原子性,一个事务要么全部执行、要么全部不执行。 并发里的原子性和事务里的原子性有一样的内涵和概念。假定有2个操作A和B都包含多个步骤…...
真机测试——关于荣耀Magic UI系列HBuilder真机调试检测不到解决办法
出现这种状况怎么办 1、开启USB调试 2、重点来了——我们要选择USB配置,选择音频来源 3、连接OK...
代理IP安全问题:在国外使用代理IP是否安全
目录 前言 一、国外使用代理IP的安全风险 1. 数据泄露 2. 恶意软件 3. 网络攻击 4. 法律风险 二、保护国外使用代理IP的安全方法 1. 选择可信的代理服务器 2. 使用加密协议 3. 定期更新系统和软件 4. 注意网络安全意识 三、案例分析 总结 前言 在互联网时代&…...
SonarLint 疑难语法修正
/*** 投诉率统计(厂端)* 1.通过售后小区分组统计* 2.通过经销商分组统计* param kpiComplaintRateQueryVO 查询参数* return 投诉率统计数据*/ApiOperation(value "厂端投诉率统计维度查询")PostMapping("/vcdc/ratestatis")public List<KpiComplaintR…...
MurmurHash算法
MurmurHash:(multiply and rotate) and (multiply and rotate) Hash,乘法和旋转的hash 算法。 一、哈希函数 散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“…...
CSRF靶场实战
DVWA靶场链接:https://pan.baidu.com/s/1eUlPyB-gjiZwI0wsNW_Vkw?pwd0b52 提取码:0b52 DVWA Low 级别打开靶场,修改密码 复制上面的 url,写个简单的 html 文件 <html <body> <a hrefhttp://127.0.0.1/DVWA/vulne…...
小程序性能优化
背景 在开发小程序的过程中我们发现,小程序的经常会遇到性能问题,尤其是在微信开发者工具的时候更是格外的卡,经过排查发现,卡顿的页面有这么多的js代码需要加载,而且都是在进入这个页面的时候加载,这就会…...
C++拿几道题练练手吧
第 1 题 【 问答题 】 • 最短路径问题 平面上有n个点(n<100),每个点的坐标均在-10000~10000之间。其中的一些点之间有连线。 若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点间的直线距离。现在的任务…...
【国产MCU】-CH32V307-I2C控制器
I2C控制器 文章目录 I2C控制器1、I2C模块介绍2、I2C驱动API介绍3、I2C使用实例3.1 主模式3.1.1 主设备发送模式和主设备接收模式3.1.2 DMA方式发送3.2 从模式内部集成电路总线(I2C)广泛用在微控制器和传感器及其他片外模块的通讯上,它本身支持多主多从模式,仅仅使用两根线(…...
k8s pod理论
一、Pod概述 1、Pod的定义 Pod是K8S中创建和管理的最小单位。 2、一个Pod至少包含多少容器 1个pause容器(基础容器/父容器/根容器)和 1个或者多个应用容器(业务容器) 通常一个Pod最好只包含一个应用容器,一个应用容…...
智慧应急:构建全方位、立体化的安全保障网络
一、引言 在信息化、智能化快速发展的今天,传统的应急管理模式已难以满足现代社会对安全保障的需求。智慧应急作为一种全新的安全管理模式,旨在通过集成物联网、大数据、云计算、人工智能等先进技术,实现对应急事件的快速响应、精准决策和高…...
国际黄金价格是什么?和黄金价格有何区别?
黄金是世界上最珍贵的贵金属之一,其价值被无数人所垂涎。而国际黄金价格作为市场上的参考指标,直接影响着黄金交易的买卖。那么国际黄金价格到底是什么,与黄金价格又有何区别呢?本文将为您详细解答。 国际黄金价格是指以美元计量的…...
React入门简介
React简介 react是Facebook用来创建用户界面的js库。React不是一个MVC框架,而是一个用于构建组件ui库,是一个前端界面开发工具,所以很多人认为React是MVC中的V(视图)。React的存在能够很好的解决‘构建随着时间数据不断…...
强化学习_06_pytorch-PPO实践(Hopper-v4)
一、PPO优化 PPO的简介和实践可以看笔者之前的文章 强化学习_06_pytorch-PPO实践(Pendulum-v1) 针对之前的PPO做了主要以下优化: batch_normalize: 在mini_batch 函数中进行adv的normalize, 加速模型对adv的学习policyNet采用beta分布(0~1): 同时增加MaxMinScale …...
Scala Intellij编译错误:idea报错xxxx“is already defined as”
今天写scala代码时,Idea报了这样的错误,如下图所示: 一般情况下原因分两种: 第一是我们定义的类或对象重复多次出现,编译器无法确定使用哪个定义。 这通常是由于以下几个原因导致的: 重复定义:在同一个文件…...
面试笔记系列五之MySql+Mybaits基础知识点整理及常见面试题
myibatis执行过程 1读取MyBatis的配置文件。 mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。 2加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中…...
掌握Pillow:Python图像处理的艺术
掌握Pillow:Python图像处理的艺术 引言Python与图像处理的概述Pillow库基础导入Pillow库基本概念图像的打开、保存和显示 图像操作基础图像的剪裁图像的旋转和缩放色彩转换和滤镜应用文字和图形的绘制 高级图像处理图像的合成与蒙版操作像素级操作与图像增强复杂图形…...
React最常用的几个hook
React最常用的几个Hook包括:useState、useEffect、useRef以及useContext。 useState: 用于在函数组件中添加状态管理。它返回一个数组,第一个元素是当前状态的值,第二个元素是更新状态的函数。在使用时,可以通过解构赋…...
自然语言处理Gensim入门:建模与模型保存
文章目录 自然语言处理Gensim入门:建模与模型保存关于gensim基础知识1. 模块导入2. 内部变量定义3. 主函数入口 (if __name__ __main__:)4. 加载语料库映射5. 加载和预处理语料库6. 根据方法参数选择模型训练方式7. 保存模型和变换后的语料8.代码 自然语言处理Gens…...
Windows 10中Visual Studio Code(VSCode)无法自动打开终端的解决办法
1.检查设置: 打开VSCode。点击左侧菜单栏的“文件”(File)。选择“首选项”(Preferences)。点击“设置”(Settings)。在搜索框中输入“shell”,然后点击“settings.json”进行编辑。…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章 摘要: 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言,受限于 C 语言本身的内存安全和并发安全问题,开发复杂模块极易引入难以…...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...
