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

【多线程】有了解过 CAS 和原子操作吗?

SueWakeup

                                                      个人主页:SueWakeup

                                                      系列专栏:学习Java

                                                      个性签名:人生乏味啊,我欲令之光怪陆离 

本文封面由 凯楠📷 友情赞助!

目录

前言

悲观锁和乐观锁

什么是 CAS ?

什么是原子操作?        

CAS 执行流程 

Java 中的原子操作类(基于 CAS 的 AtomicInteger)

Unsafe 

Unsafe 实现 CAS 的工作原理

CAS 的缺点

注:手机端浏览本文章可能会出现 “目录”无法有效展示的情况,请谅解,点击侧栏目录进行跳转    


前言

        在高并发的业务场景下,线程安全可以通过 synchronized 或 Lock 来保证同步,从而达到线程安全的目的。但是, synchronized 或 Lock 都是基于互斥锁的思想实现,加锁和释放锁的过程中都会带来性能损耗问题。

        除了 synchronized 或 Lock 以外,还可以通过 JUC(java.util.concurrent.xxx)提供的 CAS 机制实现无锁的解决方案,它是基于乐观锁的思想方案,实现非阻塞的同步方式,从而保证线程安全。


悲观锁和乐观锁

  • 悲观锁
    • 在持有数据的时候将 资源 或 数据 加锁,认为数据很可能会被其他人所修改(线程冲突)
    • 适用于写多读少的场景,避免频繁失败和重试影响性能
  • 乐观锁
    • 程序不加锁,认为资源和数据不会被别人所修改(线程冲突),但在进行写入操作的时候会判断当前数据是否被修改过,一旦有冲突发生,通常采用一种称为 CAS 的技术保证线程执行的安全性
    • 适用于读多写少的场景,避免加锁影响性能

什么是 CAS ?

  • CAS 全称 “比较并交换”(Compare And Swap)。是一种原子操作
  • 现代 CPU 广泛支持的一种对内存中的共享数据进行操作的特殊指令。进行读写操作时, CPU 会比较内存中某个值是否和预期的值相同,如果相同则将这个值更新为新值,不相同则不做更新
  • 无锁的线程同步解决方案,基于 “乐观锁” 思想的操作,保证多线程并发中保障共享资源的原子操作,相对于 synchronized 或 Lock 来说,是一种轻量级的实现方案
  • 使用案例:
    • Java 的核心类库中 AtomicInteger、ConcurrentHashMap 都是基于 CAS 机制实现原子操作(Java 中的 CAS 机制是通过 Unsafe 类提供的 compareAndSwapXXX() 等 CAS 方法实现,底层通过 CPU 指令 cmpxchg 实现)

什么是原子操作?        

        原子操作是指不能被线程调度打断的操作。通常是一系列操作,该系列操作从执行开始到执行结束期间不会发生线程切换(原子操作不能被中断)。


CAS 执行流程 

假设内存中存在一个变量 i,它在内存中对应的值是 A(第一次读取) ,此时经过业务计算处理后,结算结果为新值 B,在更新之前会再去读 i 现在的值 C。如果 A 和 C 相同,代表业务计算处理的过程中 i 的值并没有发生变化,才会把 i 更新(交换)为新值 B。如果不相同,那说明在业务计算时,i 的值发生了变化,则不进行更新操作。最后 CPU 会将旧的数值返回。

总结:要更新的值是否等于旧值,如果等于,将该值设置为新值;如果不等于,将旧数值返回


Java 中的原子操作类(基于 CAS 的 AtomicInteger)

  • Java 无法控制线程的切换,所以 Java 中 CAS 操作采用 native 方法,底层采用 C  或 C++ 实现

        AtomicInteger 是 java.util.concurrent.atomic 包下的一个子类,该包下还有 AtomicBoolean ,AtomicLong,AtomicLongArray,AtomicReference 等原子类,主要用于在高并发环境下,保证线程安全。

AtomicInteger 常用方法

// AtomicInteger 常用方法public final int get(); // 获取当前值
public final int getAndSet(int newValue); // 获取当前的值,并设置新的值
public final int getAndIncrement(); // 获取当前的值,并自增 1
public final int getAndDecrement(); // 获取当前的值,并自减 1
public final int getAndAdd(int delta); // 获取当前的值,并自增指定值

AtomicInteger 核心源码 

public class AtomicInteger extends Number implements java.io.Serializable {//底层访问对象private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {//基于 Unsafe 对象获取 value 字段相对当前对象的“起始地址”的偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value;// 获取当前的值public AtomicInteger(int initialValue) {value = initialValue;}/* 获取当前的值,并自增 1* 1.this:当前的实例* 2.valueOffset:value实例变量的偏移量* 3.delta:自增 1*/public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);}/* 获取当前的值,并自增指定值* 1.this:当前的实例* 2.valueOffset:value实例变量的偏移量* 3.delta:自增指定值*/public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);}}

Unsafe 

        在 AtomicInteger 核心源码中,可以看到 CAS 机制是通过 Unsafe 类实现。

        sum.misc.Unsafe 是 JDK 提供的一个底层工具类。它提供内存操作、CAS、对象操作等 “不安全” 的功能,来让 JDK 能够使用 Java 代码来实现原本需要使用 native 本地方法才可以实现的功能。由于,该类不应该在 JDK 核心类库之外使用,所以被命名为 Unsafe(不安全)


Unsafe 实现 CAS 的工作原理

AtomicInteger 类

    public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}

Unsafe 类

     public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}

        首先读取当前对象 var1 在主内存中的值,并保存到 var5 中,然后通过循环,判断当前对象在主内存中的值是否等于 var5,如果相同,就自增(交换 var5 与 var5 + var4 两个值),否则继续循环,重新获取 var 值。

        在上述逻辑中核心方法是 compareAndSwapInt()方法,它是一个 native 方法,这个方法编译后的 CPU 指令是 cmpxchg,该指令连续执行,不会被打断,所以可以保证原子性。

        在 getAndAddInt()方法中通过 do...while 循环操作实现自旋锁:当预期值和主内存中的值不等时,就重新获取主内存中的值。


CAS 的缺点

  • 循环时间长,开销大
    • 在 Unsafe 的实现中使用了自旋锁的机制。在该环境如果 CAS 操作失败,就需要循环进行 CAS 操作(do...while 循环同时读取最新的期望值),如果长时间都不成功的话,那么会造成 CPU 极大的开销
  • 只能保证一个共享变量的原子操作
    • 在最初的实例中,可以看出是针对一个共享变量使用了 CAS 机制,可以保证原子性操作。但如果存在多个共享变量,或一整个代码块的逻辑需要保证线程安全,CAS 就无法保证原子性操作
    • 此时,需要考虑采用加锁方式(悲观锁)保证原子性
  • ABA 问题
    • 线程 P1 在共享变量中读到值 A
    • 线程 P1 被抢占,线程 P2 开始执行
    • 线程 P2 把共享变量里的值从 A 改成了 B,再改回到 A
    • 线程 P2 被抢占,线程 P1 开始执行
    • 线程 P1 回来看到共享变量里的值没有被改变,继续执行

可以通过 JDK 的 Atomic 包中的 AtomicStampedReference 类来解决 ABA问题:使用 compareAndSet 方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

相关文章:

【多线程】有了解过 CAS 和原子操作吗?

SueWakeup 个人主页:SueWakeup 系列专栏:学习Java 个性签名:人生乏味啊,我欲令之光怪陆离 本文封面由 凯楠📷 友情赞助! 目录 前言 悲观锁和乐观锁 什么是 CAS ? 什么是原子操作? CAS 执行流…...

Linux 服务升级:Nginx 热升级 与 平滑回退

目录 一、实验 1.环境 2.Kali Linux 使用nmap扫描CentOS 3.Kali Linux 远程CentOS 4.Kali Linux 使用openvas 扫描 CentOS 5.Nginx 热升级 6.Nginx 平滑回退 二、问题 1.kill命令的信号有哪些 2.平滑升级与回退的信号 一、实验 1.环境 (1)主机…...

能降低嵌入式系统功耗的三个技术

为电池寿命设计嵌入式系统已经成为许多团队重要的设计考虑因素。优化电池寿命的能力有助于降低现场维护成本,并确保客户不需要不断更换或充电电池,从而获得良好的产品体验。 团队通常使用一些标准技术来提高电池寿命,例如将处理器置于低功耗…...

暴力快速入门强化学习

强化学习算法的基本思想(直觉) 众所周知,强化学习是能让智能体实现某个具体任务的强大算法。 强化学习的基本思想是让智能体跟环境交互,通过环境的反馈让智能体调整自己的策略,从反馈中学习,不断学习来得到…...

vue中v-if和v-show的区别

手段:v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐;编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-s…...

MATLAB绘图

现学现用,用时再学。 plot函数:有两个向量被指定为参数,plot(x,y) 会生成 y 对 x 的图形 添加轴标签和标题: 通过调用一次 plot,多个 x-y 对组参数会创建多幅图形: 在每十个数据点处放置标记: 一个窗口绘制多个图形; 可在弹窗的插入选项上添加…...

嵌入式学习-ARM-Day4

嵌入式学习-ARM-Day4 实现三个LED灯亮灭 .text .global _start _start: 使能GPIOE的外设时钟 RCC_MP_AHB4ENSETR的第[4]设置为1即可使能GPIOE时钟 LED1 LDR R0,0X50000A28 指定寄存器地址 LDR R1,[R0] 将寄存器原来的数值读取出来,保存到R1中 ORR R1,R1,#(0x…...

MySQL 中的事务和存储引擎

目录 事务的 ACID 特性 MySQL 的四种隔离机制和问题 MySQL 的四种隔离机制: MySQL 的存储引擎 InnoDB 存储引擎 MyISAM 存储引擎 Memory 存储引擎 通过 ALTER TABLE 语句更改存储引擎 在创建表时指定存储引擎 通过修改配置文件设置默认存储引擎 在数据库系…...

echarts多个折线图共用一个x轴和tooltip组件

实现效果 根据接口传来的数据&#xff0c;使用echarts绘制出&#xff0c;共用一个x轴的图表 功能&#xff1a;后端将所有数据传送过来&#xff0c;前端通过监听选中值来展示对应的图表数据 数据格式&#xff1a; 代码&#xff1a; <template><div><div clas…...

wireshark数据捕获实验简述

Wireshark是一款开源的网络协议分析工具&#xff0c;它可以用于捕获和分析网络数据包。是一款很受欢迎的“网络显微镜”。 实验拓扑图&#xff1a; 实验基础配置&#xff1a; 服务器&#xff1a; ip:172.16.1.88 mask:255.255.255.0 r1: sys sysname r1 undo info enable in…...

如何利用RunnerGo简化性能测试流程

在软件开发过程中&#xff0c;测试是一个重要的环节&#xff0c;需要投入大量时间和精力来确保应用程序或网站的质量和稳定性。但是&#xff0c;随着应用程序变得更加复杂和庞大&#xff0c;传统的测试工具在面对比较繁琐的项目时非常费时费力。这时&#xff0c;一些自动化测试…...

继承和深拷贝封装

继承和深拷贝封装 今日目标&#xff1a; 1.es5寄生组合式继承 2.es6类的继承 3.深拷贝函数封装 00-回顾 # 不同数据类型赋值时的区别&#xff1a; 基本数据类型&#xff0c;赋的就是值&#xff0c;相互之间不再有任何影响 引用数据类型&#xff0c;赋的是地址&#xff0c…...

《定时执行专家》:Nircmd 的超级搭档,解锁自动化新境界

目录 Nircmd 简介 《定时执行专家》与 Nircmd 的结合 示例&#xff1a; 自动清理电脑垃圾: 定时发送邮件: 定时关闭电脑: 《定时执行专家》的优势: 总结: 以下是一些其他使用示例&#xff1a; 立即下载《定时执行专家》&#xff1a; Nircmd 官方网站&#xff1a; 更…...

Android 封装的工具类

文章目录 日志封装类-MyLog线程封装类-LocalThreadPools自定义进度条-LoadProgressbar解压缩类-ZipUtils本地数据库类-MySQLiteHelper访问webservice封装-HttpUtilsToolbar封装类-MaterialToolbar网络请求框架-OkGo网络请求框架-OkHttp 日志封装类-MyLog 是对android log的封装…...

linux下线程分离属性

linux下线程分离属性 一、线程的属性---分离属性二、线程属性设置2.1 线程创建前设置分离属性2.2 线程创建后设置分离属性 一、线程的属性—分离属性 什么是分离属性&#xff1f; 首先分离属性是线程的一个属性&#xff0c;有了分离属性的线程&#xff0c;不需要别的线程去接合…...

Leetcode 208. 实现 Trie (前缀树)

心路历程&#xff1a; 一道题干进去了一个下午&#xff0c;单纯从解题角度可以直接用python的集合就很简单地解决&#xff08;不知道是不是因为python底层的set()类&#xff09;。后来从网上看到这道题应该从前缀树的角度去做&#xff0c;于是花了半个多小时基于字典做了前缀树…...

蓝桥杯练习题——健身大调查

在浏览器中预览 index.html 页面效果如下&#xff1a; 目标 完成 js/index.js 中的 formSubmit 函数&#xff0c;用户填写表单信息后&#xff0c;点击蓝色提交按钮&#xff0c;表单项隐藏&#xff0c;页面显示用户提交的表单信息&#xff08;在 id 为 result 的元素显示&#…...

React——组件通讯

组件通讯介绍 组件中的状态是私有的&#xff0c;组件的状态只能在组件内部使用&#xff0c;无法直接在组件外使用&#xff0c;但是我们在日常开发中&#xff0c;通常会把相似、功能完整的应用才分成组件&#xff08;工厂模式&#xff09;利于我们的开发&#xff0c;而不同组件直…...

php闭包应用

laravel 路由 bingTo 把路由URL映射到匿名回调函数上&#xff0c;框架会把匿名回调函数绑定到应用对象上&#xff0c;这样在匿名函数中就可以使用$this关键字引用重要的应用对象。Illuminate\Support\Traits\Macroable的__call方法。 自己写一个简单的demo: <?php <?…...

基于python+vue的OA公文发文管理系统flask-django-php-nodejs

系统根据现有的管理模块进行开发和扩展&#xff0c;采用面向对象的开发的思想和结构化的开发方法对OA公文发文管理的现状进行系统调查。采用结构化的分析设计&#xff0c;该方法要求结合一定的图表&#xff0c;在模块化的基础上进行系统的开发工作。在设计中采用“自下而上”的…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

ubuntu22.04 安装docker 和docker-compose

首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 &#xff09; 缓存工作原理分析 在了解了本地缓存和远程缓存之后&#xff0c;我们来探究缓存是如何工作的。以计算文件的哈希串为例&#xff0c;若后续运行任务时文件哈希串未变&#xff0c;系统会直接使用对应的输出和制品文件。 2 …...

[USACO23FEB] Bakery S

题目描述 Bessie 开了一家面包店! 在她的面包店里&#xff0c;Bessie 有一个烤箱&#xff0c;可以在 t C t_C tC​ 的时间内生产一块饼干或在 t M t_M tM​ 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC​,tM​≤109)。由于空间…...