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

CASAtomic 原子操作详解

文章目录

  • CAS&Atomic 原子操作详解
    • 什么是原子操作
    • CAS
    • 相关原子操作类的使用
      • AtomicInteger
      • AtomicIntegerArray
      • 更新引用类型
      • 原子更新字段类
      • LongAdder

CAS&Atomic 原子操作详解

什么是原子操作

Mysql事务中的原子性就是一个事务中执行的多条sql,要么同时成功,要么同时失败,他们不可拆分。并发中的原子操作也一样,多个线程中,站在线程A的角度看线程B的操作,线程B的操作就是一个原子的;站在线程B的角度看线程A,线程A的操作是原子的。一整个操作要么全部执行完了,要么就没有执行,中间不能拆分。

那么要怎么实现原子性嘞?可以使用synchronized锁来保证一段代码的原子性,但是加锁影响性能,甚至还有死锁方面的问题需要考虑。

所以锁机制是比较重量级的,粒度较大的一种机制,比如对于计数器方面的操作来说,可能加锁的耗时都比整个计算的耗时还要高。Java 就提供了 Atomic 系列的原子操作类,在java.util.concurrent.atomic包下

这些原子操作类是基于处理器的CAS指令来实现原子性的,Compare and swap。比较并且交换



CAS

每个CAS操作过程基本上都包含三个部分:内存地址V、期望值A、新值B

期望值就是旧值,首先会去内存地址中进行比较,我期望当前这个内存地址中的值是我期望的旧值,如果是则把新值赋值到这个内存地址中,如果不是则不做任何事。在一般的使用中我们会不断尝试去进行CAS操作,直到成功为止。

Java 中的 Atomic 系列的原子操作类的实现则是利用了循环 CAS 来实现。


使用CAS实现原子操作的几个问题

  • ABA问题

    ABA问题在大多数场景下,不解决其实也没什么影响。

    解决思路:添加版本戳,在变量前面追加上版本号,每次变量更新的时候把版本号加 1,那么 A-->B-->A 就会变成 1A-->2B-->3A

  • 循环时间长,对于cpu来说开销较大

  • 只能保证一个共享变量的原子操作

    对于多个共享变量操作时就无法使用CAS来保证原子性了,这个时候还是需要用锁。

    还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。

    从 Java 1.5开始,JDK 提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行 CAS 操作。



相关原子操作类的使用

这些类的用户都大同小异,这里就拿几个典型来举例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YlKYASsZ-1680340352611)(picture/并发/image-20230401155714033.png)]



AtomicInteger

// 以原子方式将给定值添加到当前值,然后将相加后的结果返回
public final int addAndGet(int delta){}// 指定期望值与修改后的值,如果期望值和当前值相同则进行更新操作
public final boolean compareAndSet(int expect, int update) {}// 先返回当前值,然后再进行原子自增1
public final int getAndIncrement() {}// 先返回当前值,然后进行原子更新操作
public final int getAndSet(int newValue) {}

案例:

public class UseAtomicInt {static AtomicInteger ai = new AtomicInteger(10);public static void main(String[] args) {ai.getAndIncrement();ai.incrementAndGet();//ai.compareAndSet();ai.addAndGet(24);}
}



AtomicIntegerArray

提供原子的方式更新数据中的整形,常用方法如下:

// 以原子方式将给定值添加到索引 i 处的元素。然后返回更新后的值
public final int addAndGet(int i, int delta){}// 先比较,期望值和当前值相同再执行更新操作
public final boolean compareAndSet(int i, int expect, int update) {}

案例:

public class AtomicArray {static int[] value = new int[] { 1, 2 };static AtomicIntegerArray ai = new AtomicIntegerArray(value);public static void main(String[] args) {ai.getAndSet(0, 3);System.out.println(ai.get(0));//原数组不会变化System.out.println(value[0]);}
}// 输出结果
3
1Process finished with exit code 0

需要注意的是,数组 value 通过构造方法传递进去,然后 AtomicIntegerArray会将当前数组复制一份,所以当 AtomicIntegerArray 对内部的数组元素进行修改 时,不会影响传入的数组。



更新引用类型

如果要同时更新多个原子变量就需要使用更新引用类型提供的类了。Atomic提供了三个类:


AtomicReference

原子更新引用类型


案例:

public class UseAtomicReference {public 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);/*输出结果:UserInfo{name='Bill', age=17}UserInfo{name='Mark', age=15}*/}/*** 定义一个实体类*/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 "UserInfo{" +"name='" + name + '\'' +", age=" + age +'}';}}
}



AtomicStampedReference

利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在 ABA问题了


AtomicMarkableReference

原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引 用类型。

构造方法是 AtomicMarkableReference(V initialRef,booleaninitialMark)。


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() + ":当前变量值:"+ oldReference + "-当前版本戳:" + oldStamp + "\n"+ 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() + ":当前变量值:"+ reference + "-当前版本戳:" + asr.getStamp() + "\n"+ asr.compareAndSet(reference, reference + "+C", oldStamp, oldStamp + 1));}});rightStampThread.start();rightStampThread.join();errorStampThread.start();errorStampThread.join();System.out.println(asr.getReference() + "============" + asr.getStamp());}
}

输出结果

mark============0
Thread-0:当前变量值:mark-当前版本戳:0
true
Thread-1:当前变量值:mark+Java-当前版本戳:1
false
mark+Java============1



原子更新字段类

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类

Atomic 包提供了以下 3 个类进行原子字段更新。 要想原子地更新字段类需要两步。

  1. 因为原子更新字段类都是抽象类, 每次使用的时候必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

  2. 更新类的字段(属性)必须使用 public volatile修饰符。

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。

  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。

  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。



LongAdder

并发量较少,自旋的冲突也就较少。但如果并发很多的情况下,CAS机制就不如synchronized了,因为很多个线程都集中判断一个变量的值,不断的自旋,对cpu的消耗也较大,同一时刻又只会一个线程更新成功。

在JDK1.8就引入了LongAdder类,它在处理上面问题的时候是采用的一种热点数据的分散写

LongAdder中有两个成员变量

// 当为非空时,大小为 2 的幂。
// 如果并发很高就使用cell数组做写热点的分散,其中某些线程共同操作某一个数组中的元素
transient volatile Cell[] cells;// 当争抢较少时使用这个变量来进行cas,就类似于AtomicInteger类中的value变量
transient volatile long base;

然后调用sum()方法将数组cells和base变量的中做一个汇总,返回当前总和。在没有并发更新的情况下调用将返回准确的结果,但在计算总和时发生的并发更新可能不会合并,所以sum()方法并不能保证强一致性,它返回的只是一个近似值

// 可以看到 sum()方法没有任何加锁的逻辑
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;
}

相关文章:

CASAtomic 原子操作详解

文章目录CAS&Atomic 原子操作详解什么是原子操作CAS相关原子操作类的使用AtomicIntegerAtomicIntegerArray更新引用类型原子更新字段类LongAdderCAS&Atomic 原子操作详解 什么是原子操作 Mysql事务中的原子性就是一个事务中执行的多条sql&#xff0c;要么同时成功&am…...

卷积神经网络(convolutional neural network, CNN)

卷积神经网络&#xff08;convolutional neural network, CNN&#xff09; 卷积神经网络&#xff08;convolutional neural network, CNN&#xff09;&#xff0c;是一种专门用来处理具有类似网格结构的数据的神经网络。卷积网络是指那些至少在网络的一层中使用卷积运算来替代…...

kube-apiserver启动流程源码分析

1. 概述 KubeAPIServer 主要是提供对 API Resource 的操作请求&#xff0c;为 kubernetes 中众多 API 注册路由信息&#xff0c;暴露 RESTful API 并且对外提供 kubernetes service&#xff0c;使集群中以及集群外的服务都可以通过 RESTful API 操作 kubernetes 中的资源。 2…...

Scala基础(二)

单例对象&#xff08;object&#xff09; Scala的类中无法定义静态成员&#xff0c;即无static关键字。如何像Java一样表达类的静态成员变量、成员方法与静态代码块&#xff1f; Scala解决方案&#xff1a;单例对象 使用“object”关键字声明&#xff0c;可包含变量、方法与…...

Python 生产者消费者模型是什么?

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注&#xff01; 作者| 慕课网精英讲师 朱广蔚 1. 简介 生产者和消费者问题是线程模型中的经典问题&#xff1a; 生产者和消费者共享同一个存储空间生产者往存储空间中添…...

手机银行评测系列:北京银行“京彩生活”7.0从用户视角出发,实现沉浸式体验重塑

易观&#xff1a;2023年3月28日&#xff0c;北京银行发布“京彩生活”APP 7.0版本&#xff0c;从旅程再造、特色金融、场景生态、平台联动、协同经营、体验管理和安全守护七大方面全面升级&#xff0c;从用户视角出发&#xff0c;重塑用户旅程&#xff0c;简化操作流程&#xf…...

ZJYC2023 浙江省大学生程序设计竞赛校内选拔赛部分题解 C J B L

ZJYC2023 浙江省大学生程序设计竞赛校内选拔赛部分题解 C J B L 难度分布&#xff1a; 签到&#xff1a;CJ Easy&#xff1a;BL Midium&#xff1a;IAGKFE Hard&#xff1a;DH 题解&#xff1a; 签到&#xff1a;CJ C - ^{-1} 参考代码&#xff1a; #include<bits/std…...

百科创建:7种有效的百科词条创建技巧

百科词条是互联网上最常见的知识信息资源之一&#xff0c;它们是人们查找信息的主要途径之一。创建一个高质量的百科词条并不是一件容易的事情&#xff0c;需要一些技巧和经验才能做到。下面是一些创建百科词条的技巧&#xff1a; 一、确保词条的独特性 在创建百科词条之前&…...

ThreeJS-dat.gui界面控制颜色、隐藏、位置(六)

下载组件dat.gui npm install dat.gui -S 引入组件 import * as dat from dat.gui //界面控制 代码&#xff1a; <template> <div id"three_div"> </div> </template> <script> import * as THREE from "three"; import {O…...

接口自动化测试,完整入门篇

目录 1. 什么是接口测试2. 基本流程3. 需求分析4. 用例设计5. 脚本开发6. 结果分析7. 完整脚本8. 参考资料1. 什么是接口测试 顾名思义&#xff0c;接口测试是对系统或组件之间的接口进行测试&#xff0c;主要是校验数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及…...

利用ControlNet重新定义你的AI姿势

利用ControlNet重新定义你的AI姿势 前段时间给大家分享了如何利用colab实现AI绘画自由&#xff0c;现在Stable Diffusion WebUI Colab TW又更新了不少新功能。最重要的是可以通过谷歌硬盘的快捷方式导入模型&#xff0c;极大的节省了谷歌硬盘容量。 众所周知&#xff0c;谷歌…...

中医药NER命名实体识别基于SPANNER方式

一个不知名大学生&#xff0c;江湖人称菜狗 original author: Jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2023.3.5 Last edited: 2023.3.5 导读 本文使用SPANNER方式实现对中医药进行实体识别&#xff0c;采用focal loss 进行优化。 本文章作用防止安静…...

Vue必掌握

目录 一、组件通信方式 二、v-if和v-for 三、生命周期 1、描述 2、setup和created谁先执行 3、setup中为什么没有beforeCreate和created 四、双向绑定 v-model 1、定义 2、本质&#xff0c;原理 3、好处 五、如何扩展一个组件 1、mixins 缺点 2、slot插槽 3、e…...

SSM部分

声明式事务 从之前的事务控制的代码中可以看出&#xff0c;是有规律可循&#xff0c;代码的结构基本是确定的&#xff0c;所以框架就可以将固定模式的代码抽取出来&#xff0c;进行相关的封装。 封装起来后&#xff0c;我们只需要在配置文件中进行简单的配置即可完成操作。 …...

【Springboot系列】Springboot接管所有Controller,magic-api源码阅读

系列文章地址:Spring Boot学习大纲,可以留言自己想了解的技术点 最近在项目中使用了一个第三方的包 magic-api,节省了很多的时间,整体来说就是只用写sql就好了,不用写service,controller那些,全部统一处理了。 具体的使用大家可以搜索下,网上到处都是,建议去官网看。…...

二、LED子系统数据结构详解

个人主页&#xff1a;董哥聊技术我是董哥&#xff0c;嵌入式领域新星创作者创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01;文章目录1、核心数据结构1.1 gpio_led_platform_data1.2 gpio_leds_priv1.3 gpio_led1.4 gpio_led_data1.5 led_…...

Kubernetes(11):数据存储详解

在前面已经提到,容器的生命周期可能很短,会被频繁地创建和销毁。那么容器在销毁时,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器的数据,kubernetes引入了Volume的概念。 Volume是Pod中能够被多个容器访问的共享目录…...

随想录Day43--动态规划: 1049. 最后一块石头的重量 II , 494. 目标和 , 474.一和零

最后一块石头重量转化为将一个集合分隔成两个集合&#xff0c;两个集合之间的差值最小&#xff0c;就是最后剩下最小的石头重量。这里可以求集合的一个平均值&#xff0c;如果正好等于平均值&#xff0c;说明可以抵消&#xff0c;这时候重量为0&#xff0c;如果不行&#xff0c…...

Qt中对TCP粘包的处理

当时用TCP协议传输数据时&#xff0c;经常出现粘包的现象 当服务器向客户端发送数据之后&#xff0c;客户端还没有接收数据的时候&#xff0c;这段时间数据在什么地方&#xff1f; 1、服务器&#xff1f;服务器已经发出数据了 2、网线&#xff1f;数据应该在内存&#xff0c;怎…...

贪心-单调递增的数字

当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 示例 1: 输入: n 10 输出: 9示例 2: 输入: n 1234 输出: 1234示例 3: 输入…...

AVPlayer 卡顿、缓冲、加载失败问题根治与监控方案

在 iOS 音视频开发中&#xff0c;AVPlayer 作为系统原生播放器&#xff0c;凭借其稳定性、兼容性和低功耗优势&#xff0c;成为大多数 App 的首选。但在实际落地过程中&#xff0c;卡顿、缓冲异常、加载失败三大问题&#xff0c;却常常成为开发者的“拦路虎”——弱网环境下频繁…...

Claude Code提示词入门:CLAUDE.md编写完全指南

目录Claude Code提示词入门&#xff1a;CLAUDE.md编写完全指南 &#x1f3af;&#x1f4cc; 目录1. 什么是CLAUDE.md2. 为什么CLAUDE.md这么重要2.1 没有CLAUDE.md会怎样&#xff1f;2.2 有了CLAUDE.md会怎样&#xff1f;2.3 核心价值3. CLAUDE.md的加载机制3.1 加载优先级3.2 …...

倒置百分比堆叠面积图表示列详解|Highcharts大气成分图表代码

这是一个基于 Highcharts 实现的水平面积曲线图&#xff08;areaspline 倒置坐标系&#xff09;&#xff0c;专门用于展示不同高度下地球大气成分的体积占比变化&#xff0c;是典型的百分比堆叠面积图&#xff0c;数据直观反映了大气层随高度升高的成分分布规律。我会从图表结…...

RAG实战指南:让大模型学会检索外部知识

RAG&#xff1a;给 LLM 装上知识库——从原理到完整可运行系统LLM 的知识截止在训练日期。RAG 让 AI 能「查资料」回答——这是 Agent 有「长期记忆」的基础。一、为什么需要 RAG 用户&#xff1a;HarmonyOS NEXT 的 Observed 装饰器怎么用&#xff1f;没有 RAG 的 LLM&#xf…...

手把手教你学Simulink——新能源并网逆变器的最大功率点跟踪(MPPT)与并网联合仿真

目录 手把手教你学Simulink——新能源并网逆变器的最大功率点跟踪(MPPT)与并网联合仿真 一、背景与挑战 1.1 为什么新能源并网离不开 MPPT? 1.2 核心痛点与设计目标 二、系统架构与核心控制推导 2.1 整体架构:DC 级联的“能量接力棒” 2.2 核心数学推导:看穿 MPPT 的…...

MOOTDX:Python通达信数据接口的完整指南

MOOTDX&#xff1a;Python通达信数据接口的完整指南 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx MOOTDX是一个专为量化投资和股票数据分析设计的Python通达信数据接口封装库&#xff0c;它提供…...

Kubernetes API Server优化:提升集群管理效率

Kubernetes API Server优化&#xff1a;提升集群管理效率 一、Kubernetes API Server概述 1.1 API Server的角色 Kubernetes API Server是Kubernetes集群的核心组件&#xff0c;负责处理所有的REST API请求&#xff0c;是集群内部和外部通信的枢纽。它负责验证和处理请求&#…...

RCLI:统一AI开发环境的命令行工具设计与实战

1. 项目概述&#xff1a;一个面向AI应用开发的命令行利器如果你和我一样&#xff0c;经常在本地和云端服务器之间切换&#xff0c;调试各种AI模型&#xff0c;处理数据管道&#xff0c;那么你肯定对命令行&#xff08;CLI&#xff09;又爱又恨。爱的是它的高效和可编程性&#…...

云原生环境中的混沌工程实践指南

云原生环境中的混沌工程实践指南 引言 混沌工程是一种主动验证系统可靠性的方法&#xff0c;通过在生产环境中注入故障来发现潜在的系统弱点。本文将深入探讨如何在云原生环境中实施混沌工程。 一、混沌工程概述 1.1 核心概念 ┌───────────────────────…...

现有基准任务(如操纵、导航)是否足够

在人工智能与机器人技术飞速迭代的今天&#xff0c;基准任务作为衡量模型与系统能力的核心标尺&#xff0c;贯穿于技术研发、性能评估与落地应用的全流程。操纵、导航作为两类最基础、最核心的基准任务&#xff0c;长期以来支撑着机器人、具身智能等领域的进步&#xff0c;成为…...