【JUC系列-02】深入理解CAS底层原理和基本使用

news2024/11/24 20:01:03

JUC系列整体栏目


内容链接地址
【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429
【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786

深入理解cas的底层原理和基本使用

  • 一,深入理解cas底层的执行原理
    • 1,什么是cas
    • 2,cas的自旋原理
    • 3,cas的底层实现原理
    • 4,cas的缺陷以及不足
      • 4.1,长时间自旋耗费cpu资源
      • 4.2,只能有一个共享变量的操作
      • 4.4,ABA问题
    • 5,通过Atomic举例cas
    • 6,总结

一,深入理解cas底层的执行原理

1,什么是cas

在讲底层原理之前,一定需要先了解JMM的内存模型以及多线程的通信机制,可以查看我写的前几篇文章。

Compare And Swap:顾名思义,就是比较与交换的意思。cas是一个指令,底层是一个无锁的算法,可以在并发场景中保证数据的原子性。

cas主要是针对某一个变量,对该值在不同线程位置的比较与交换,通过自旋的方式,实现主内存值,工作内存的旧值和工作内存的新值直接的操作,通过比较主内存的变量值和工作内存的初始值是否相等,来判断是否可以将工作内存的新值对主内存的值进行替换的操作。cas的比较与交换是一个原子操作,要么同时成功,要么同时失败。

但是cas并不是在java层面直接去实现,而是要借助于硬件底层去实现,如下面一些常见的比较与交换的方法中,都是通过调用native方法区实现的。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

而上面的四个参数,分别对应的是:对象,偏移量,工作内存初始值,工作内存新值

2,cas的自旋原理

接下来先举一个例子,假设此时主内存中有一个值x等于10,然后有三个工作线程thread1,thread2,thread3,用K表示主内存中的x,用M表示工作线程中的x,用N表示工作线程修改后的值,假设此时三个工作线程同时去对这个工作线程的x的值做一个类加操作,在用cas的情况下,来讲一下底层的实现逻辑

假设此时线程thread1获取到了这个x值,此时线程thread1的M值为10,N值为11,随后进行比较操作,如果此时主内存的值还是10,那么CAS就会认为此时还没有线程改过这个值,此时M的值等于K,比较成功后再进行交换,此时工作内存的K对应的x值就是11

但是可能也会出现另外一种情况,此时线程thread1中M的值还是10,N为11,但是此时线程2先对主内存的值进行修改,就是此时K的值变成了11,在比较的时候,发现旧值M和主内存的K值不一样,那么就比较失败,那么就不能进行交换

如果此时直接将这次失败直接丢弃,那么很显然不能保证cas的原子性,cas内部是这样做的,他会携带最新的值继续自旋,此时线程1的M值变成了11,N值变成了12,M值再和K值相比,如果比较一致,则将最新的12交换,如果比较失败,则继续携带最新值轮询,一直重复下去,这样就能保证原子性了

在这里插入图片描述

再了解完整个代码流程之后,再来看一段AtomicInteger类的incrementAndGet()的自增操作

//var1:对象        var2:偏移量,找到对应的变量值
//var4:值1         var5:内存中的值,旧值
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        //获取旧值,内存中的值
        var5 = this.getIntVolatile(var1, var2);
        //比较与交换,如果var5和var1的值相对,则将最新值var5+var4写回
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
	//将旧值返回,返回后进行+1操作
    return var5;
}

3,cas的底层实现原理

cas是一个指令,具体如何实现的,这就得看jvm层面的逻辑了。由于这个涉及到多线程之间的通信问题,因此需要考虑线程间的可见性和有序性,在hotspot虚拟机中,由于cas本身就是一个原子指令,所以在jvm底层,就直接在方法中加了一个 #lock 的前缀指令,这样就实现了一个类似于内存屏障的功能,这样就保证了线程之间的有序性和可见性。

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

这里的第二个参数是一个偏移量,通过第一个参数的对象头加上压缩指针来确认x的位置。

在Hotspot虚拟机的底层源码中,主要的核心cas算法是通过Atomic::cmpxchg 方法来实现的。在这个方法中,第一个参数是要交换的值,第二个参数是偏移量,第三个参数是比较的值

#atomic_linux_x86.inline.hpp
inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
            : "=a" (exchange_value)
            : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
            : "cc", "memory");
  return exchange_value;

首先会判断当前的执行环境是否为多处理器环境,目前一个cpu中基本是有两个处理器的

int mp = os::is_MP();

如果上面判断是一个多核处理器架构,那么就会在方法前面加一个 Lock 前缀指令,这样就保证了后面的比较与交换之间的原子性

(LOCK_IF_MP(%4)

随后通过cmpxchg方法进行比较与交换的操作,这里的%1和%3代表的是第1个和第三个参数。并且在调用这个方法的时候,在操作系统底层会进行一个总线加锁的机制,就是一把独占锁,整体操作变成一个串行操作。

cmpxchgl %1,(%3)

如果是=a,那么代表比较与交换成功,则直接将exchange_value值返回

"=a" (exchange_value)

如果是r,那么就代表比较与交换失败,那么返回的值就是内存里面的值

"r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)

上面的源码是linux_x86系统的源码,不同的操作系统之间,其底层实现也不同

4,cas的缺陷以及不足

4.1,长时间自旋耗费cpu资源

cas是通过无锁算法来保证数据的同步的,synchronized,lock等是通过加锁的方式来保证数据的同步的,并且要涉及到用户态和内核态之间的来回切换,但是cas是不需要来回切换的,而是不断的自旋加配合底层硬件来解决

但是cas也会出现一个问题,在并发量比较大的时候,就会需要不断的进行自旋的操作,此时就会占用大量的cpu,从而影响整个系统的性能和效率。因此需要通过不同的场景来判断是否使用CAS

4.2,只能有一个共享变量的操作

虽然上面谈到了比较和交换的操作,但是整个流程都是围绕一个对象来进行的,如果存在多个对象就得多写几个cas的程序,这也是cas的一个缺陷。

4.4,ABA问题

依旧是下图,假设thread1获取到值,此时thread2也拿到了值,将值改成11,比较交换之后,此时主内存的值为11,但是thread3拿到值后,又通过比较交换将值改成了10,此时正好轮到thread1来进行比较交换,发现主内存的值和thread1的原始值是一样的,就认为此时还没有线程改了这个值,于是进行比较交换操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gMuu1LqX-1692868445542)(img/1692772659813.png)]

其实是已经被别的线程将值修改了,但是当前线程认为该值还没有被修改过。

如男女朋友分手了,但是女方又恋爱了,又分手,然后男方再次找到女方,发现女方还是单身,男方就认为这段时间女方没有恋爱,但是女方期间谈了多少次男方并不知情,并且可能给男方带回一个小朋友…,所以说,这种问题是很严重的

那么如何解决这种ABA问题,直接看Atomic里面是怎么解决的吧,在Atomic包下面有一个AtomicStampedReference 类,在该类中有一个静态内部类 Pair,里面除了一个对象的引用之外,另外增加了一个stamp的一个版本,这个有点类似于乐观锁,每被操作一次,版本号就加1,通过这种加版本号来保证这个变量是否被更改过。

private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static <T> Pair<T> of(T reference, int stamp) {
        return new Pair<T>(reference, stamp);
    }
}

其创建对象的方式如下,第一个参数表示初始值,第二个参数表示版本。

AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1,1);
// 构造方法如下
public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
}

当然了,除了加版本号之外,还有一个加标记的方式解决,其原理很简单,就是只要被加载过,那么就给他一个mark标记,在atomic原子包下面就有这么一个类 AtomicMarkableReference

public class AtomicMarkableReference<V> {
    private static class Pair<T> {
        final T reference;
        final boolean mark;
        private Pair(T reference, boolean mark) {
            this.reference = reference;
            this.mark = mark;
        }
        static <T> Pair<T> of(T reference, boolean mark) {
            return new Pair<T>(reference, mark);
        }
    }
}

在里面的静态内部类中,除了引用之外,还存在一个布尔类型的mark标志位,只有被加载过,那么就对这个标志位设置一个true。

5,通过Atomic举例cas

首先定义一个AtomicInteger的原子类,随后进行一个值更新的操作

//这里初始化为0,这个0是当前线程的初始值
AtomicInteger atomicInteger = new AtomicInteger(0);
// 0是主内存中的最新值,10是要更新的值
atomicInteger.compareAndSet(0,10);

随后再次分析这个compareAndSet方法,可以发现里面调用了这个compareAndSwapInt方法

public final boolean compareAndSet(int expect, int update) {
    //比较与交换
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

如果主内存的最新值和当前线程的初始值一致,则进行更新的操作,否则携带最新值继续进行自旋的操作。

6,总结

cas比较和交换,通过jmm的java内存模型,借助于自旋的方式不断的去进行比较与交换,在每个对象的比较与交换中,主要是通过调用jvm的native本地方法去实现,从而去调用操作系统,在操作系统底层,cas借助了锁总线的串行方法来保证整个命令的原子操作,从而可以保证最终结果的一致性。

相对于其他的重锁,cas底层采用的是无锁算法,通过#lock前缀指令达到内存屏障的效果,从而保证数据的可见性,一致性和有序性,其内部需要花费的时间,也远远小于其他的重锁,如synchronized等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/923097.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Bootstrap的类container与类container-fluid有什么区别?

阅读本文前建议先阅读下面两篇博文&#xff1a; 怎么样通过Bootstrap已经编译好(压缩好)的源码去查看符合阅读习惯的源码【通过Source Map(源映射)文件实现】 在CSS中&#xff0c;盒模型中的padding、border、margin是什么意思&#xff1f; 以下是Bootstrap的类 container 的盒…

【Java 高阶】一文精通 Spring MVC - 标签库 (八)

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

Android——基本控件(下)(十八)

1. 时钟组件&#xff1a;AnalogClock与DigitalClock 1.1 知识点 &#xff08;1&#xff09;掌握AnalogClock与DigitalClock的使用&#xff1b; 1.2 具体内容 package com.example.clockproject;import android.os.Bundle; import android.app.Activity; import android.view…

含泪总结当遇到linux文件系统根目录上的磁盘空间不足怎么办!!

那天写项目代码&#xff0c;cmake编译生成文件的时候给我说磁盘不够了..文件没法生成&#xff0c;因为当时是远程连接的&#xff0c;所以就先断了连接&#xff0c;重启了虚拟机&#xff01;好家伙重启之后因为内存不够&#xff0c;根本进不到gnu界面&#xff0c;就是想重新扩容…

一篇带你肝完Python逆向为什么要学webpack,学完之后到底又该怎么用?

目录 前言简单示例配置示例深入案例分析 总结 前言 大家好&#xff0c;我是辣条哥&#xff01; 之前讲了很多关于基础方面的内容&#xff0c;从本章开始辣条我会开始慢慢开始跟大家解析一些进阶知识以及案例 废话不多说今天我们就深入解析一下webpack&#xff0c;我们先聊一下P…

【LeetCode】 双指针,快慢指针解题

1.删除有序数组中的重复项 class Solution {public int removeDuplicates(int[] nums) {int fast 1;int slow 1;for(;fast<nums.length;fast) {if( nums[fast] !nums[fast-1] ) {nums[slow] nums[fast];slow;}}return slow;} } 2.移除元素 class Solution {public int re…

2023年高教社杯 国赛数学建模思路 - 复盘:人力资源安排的最优化模型

文章目录 0 赛题思路1 描述2 问题概括3 建模过程3.1 边界说明3.2 符号约定3.3 分析3.4 模型建立3.5 模型求解 4 模型评价与推广5 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 描述 …

【MD5加密结果不一致问题】同一个文本字符串,使用MD5加密之后,得出的加密结果居然不相同

目录 1.1、错误描述 1.2、解决方案 1.3、MD5工具类 1.1、错误描述 今天工作中&#xff0c;遇到一个奇怪的问题&#xff0c;我负责对接第三方的短信发送接口&#xff0c;接口中有一个入参是sign加签字段&#xff0c;根据短信内容进行MD5加密 之后得到&#xff0c;于是我就是…

STM32使用PID调速

STM32使用PID调速 PID原理 PID算法是一种闭环控制系统中常用的算法&#xff0c;它结合了比例&#xff08;P&#xff09;、积分&#xff08;I&#xff09;和微分&#xff08;D&#xff09;三个环节&#xff0c;以实现对系统的控制。它的目的是使 控制系统的输出值尽可能接近预…

基于Llama2和LangChain构建本地化定制化知识库AI聊天机器人

参考&#xff1a; 本项目 https://github.com/PromtEngineer/localGPT 模型 https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML 云端知识库项目&#xff1a;基于GPT-4和LangChain构建云端定制化PDF知识库AI聊天机器人_Entropy-Go的博客-CSDN博客 1. 摘要 相比OpenAI的…

背包问题DP(01背包 完全背包 多重背包 分组背包)

目录 背包问题的简介背包问题的定义背包问题的分类 01背包问题典型例题实现思路二维数组代码实现一维数组优化实现扩展&#xff1a;记忆化搜索 DPS 实现 01背包之恰好装满思路代码实现 完全背包问题典型例题思路分析二维数组代码实现一维数组优化实现 多重背包问题多重背包问题…

网易一面:单节点2000Wtps,Kafka怎么做的?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如网易、有赞、希音、百度、网易、滴滴的面试资格&#xff0c;遇到一几个很重要的面试题&#xff1a; 问题1&#xff1a;单节点2000Wtps&#xff0c;Kafka高性能原理是什么&#…

测试人员如何通过AI提高工作效率!

随着AI技术的兴起&#xff0c;像OpenAI推出的ChatGPT、Microsoft发布的Microsoft 365 Copilot、阿里的通义千问、百度的文心一言、华为的盘古大模型等。很多测试人员开始担心&#xff0c;岗位是否会被AI取代&#xff1f;其实取代你的不是AI&#xff0c;而是会使用AI的测试人&am…

[论文分享]VOLO: Vision Outlooker for Visual Recognition

VOLO: Vision Outlooker for Visual Recognition 概述 视觉 transformer&#xff08;ViTs&#xff09;在视觉识别领域得到了广泛的探索。由于编码精细特征的效率较低&#xff0c;当在 ImageNet 这样的中型数据集上从头开始训练时&#xff0c;ViT 的性能仍然不如最先进的 CNN。…

解密长短时记忆网络(LSTM):从理论到PyTorch实战演示

目录 1. LSTM的背景人工神经网络的进化循环神经网络&#xff08;RNN&#xff09;的局限性LSTM的提出背景 2. LSTM的基础理论2.1 LSTM的数学原理遗忘门&#xff08;Forget Gate&#xff09;输入门&#xff08;Input Gate&#xff09;记忆单元&#xff08;Cell State&#xff09;…

【洛谷】P1678 烦恼的高考志愿

原题链接&#xff1a;https://www.luogu.com.cn/problem/P1678 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 将每个学校的分数线用sort()升序排序&#xff0c;再二分查找每个学校的分数线&#xff0c;通过二分找到每个同学估分附近的分数线。 最后…

【Java】对象与类

【Java】对象与类 文章目录 【Java】对象与类1、学习背景2、定义&使用2.1 创建类2.2 创建对象 3、static关键字3.1 修饰变量3.2 修饰方法3.3 修饰代码块3.4 修饰内部类 4、this关键字5、封装特性5.1 访问修饰符5.2 包的概念 6、构造方法7、代码块7.1 普通代码块7.2 成员代码…

信息安全:入侵检测技术原理与应用.(IDS)

信息安全&#xff1a;入侵检测技术原理与应用. 入侵检测是网络安全态势感知的关键核心技术&#xff0c;支撑构建网络信息安全保障体系。入侵是指违背访问目标的安全策略的行为。入侵检测通过收集操作系统、系统程序、应用程序、网络包等信息&#xff0c;发现系统中违背安全策略…

无公网IP内网穿透使用vscode配置SSH远程ubuntu随时随地开发写代码

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

【QT5-自我学习-线程qThread移植与使用-通过代码完成自己需要功能-移植小记3】

【QT5-自我学习-线程qThread移植与使用-通过代码完成自己需要功能-移植小记3】 1、前言2、实验环境3、自我总结&#xff08;1&#xff09;文件的编写&#xff08;2&#xff09;信号与槽的新理解&#xff08;3&#xff09;线程数据的传递 4、移植步骤第一步&#xff1a;添加新文…