CAS 的使用场景 CAS的ABA问题的优化 以及 synchronized 的三大优化

news2024/9/28 23:33:34

目录

🎈专栏链接:多线程相关知识详解 

一.什么是CAS

二.CAS最常用的两个场景

Ⅰ.实现原子类

Ⅱ.实现自旋锁

三.CAS的ABA问题

四.优化解决CAS的ABA问题

五.synchronized的优化 

Ⅰ.锁升级/锁膨胀

Ⅱ.锁消除

Ⅲ.锁粗化


一.什么是CAS

CAS(Compare-And-Swap)(即比较和交换),它是一条CPU并发原语,用于判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的.

它包含了三个参数:V,A,B。
V表示要读写的变量内存地址

A表示旧的预期值

B表示准备设置的新值

把内存中的某个值,和CPU寄存器A中的值,进行比较~ 如果两个值相同,就把CPU中另一个寄存器B的值和内存的值进行交换(即把内存的值写到寄存器B,同时把B的值写到内存里),是通过一个CPU指令完成的,是原子的,同时还高效(不涉及到锁冲突,线程等待)

二.CAS最常用的两个场景

Ⅰ.实现原子类

因为count++在多线程的环境下,线程是不安全的,想要线程安全就得加锁----而加锁会导致性能大打折扣,可以基于CAS实现"原子"的++操作,从而保证线程安全&高效

例如:AtomicInteger类

下面这些为AtomicInteger的伪代码

class AtomicInteger{
    private int value;

    public int getAndIncrement(){//相当于count++
        int oldValue = value;//此次oldValue相当于是寄存器A,是把内存value值读取到寄存器里
        //下面这个CAS 是比较看value这个内存中的值,是否适合寄存器A的值相同,如果相同,就把寄存器B里的值给设置到value中,同时CAS返回true,结束循环;如果不相同,则无事发生,CAS返回false,进入循环,在循环体里面,重新读取内存里的值到寄存器A中
        while ( CAS(value,oldValue,oldValue+1) != true){//此次的oldValue+1 把它理解为另一个寄存器B的值
            oldValue = value;
        }
        return oldValue;
    }
}

 AtomicInteger类的原子++使用:

import java.util.concurrent.atomic.AtomicInteger;

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger(0);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();//相当于原子类的count++
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

运行结果:

在AtomicInteger类当中还有原子的--操作等等.

Ⅱ.实现自旋锁

自旋锁是纯用户态轻量级锁也是一个乐观锁,当发现锁被其他线程持有的时候,就不会挂起等待,而是会反复询问,看当前的锁是否被释放了

反复询问就是为了抢占执行,节省了进入内核和系统调度的开销

自旋锁是属于消耗CPU资源来换取第一时间获取到锁,如果当前预期锁竞争不太激烈的时候(预期短时间内获取到锁),使用自旋锁就非常合适了

以下为CAS实现自旋锁的伪代码

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
            //比较owner和null是否相同(是否是解锁的状态)
            //如果是就要进行交换,把当前调用lock的线程的值,设置到owner里面,相当于加锁成功,结束循环
            //如果owner不为null,CAS就不会进行交换,返回false,重新进入循环,重新进行判定
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

三.CAS的ABA问题

面试的时候谈到CAS就大概率会问ABA问题

什么是CAS的ABA问题呢?

在CAS中,进行比较的时候,会发现寄存器A和内存M的值相同,但是无法判定是M始终没变,还是M变了,又重新变回来了

举个例子:

当你要去银行取钱的时候,里面有1000存款,想要从ATM中取出500存款,结果取钱的时候机器卡了以下,你多按了几下取钱操作,ATM就创建了多个线程来进行扣款操作,并且该扣款操作是基于CAS来实现的

此时并不会出现大问题,可是如果多了个线程t3,在t2还没进行CAS的时候给我汇款了500

 就会出现重复扣款的情况

四.优化解决CAS的ABA问题

只需要多出一个数据来记录内存中数据的变化,就可以解决这个问题了

针对上面这个例子,可以另外搞一个内存,用来记录M的"修改次数"(版本号)[只增不减的]或者是"上次修改时间"[只增不减],通过这个办法解决ABA问题

此时的修改操作,就不是把账户余额读取到 寄存器A中了,CAS比较的也不是账户余额,而是比较版本号/上次修改时间

 比较的时候发现版本号/上次修改时间变了,就知道账户余额发生了其他变化,就不会进行扣款操作了

五.synchronized的优化 

Ⅰ.锁升级/锁膨胀

synchronized的作用是 加锁 ,当两个线程针对同一个对象加锁的时候,就会出现锁竞争,后来尝试加锁的线程就会进行阻塞等待,直到前面一个线程释放了锁

synchronized加锁的具体过程:

Ⅰ.偏向锁

Ⅱ.轻量级锁

Ⅲ.重量级锁

 synchronized是自适应的

如果当前锁竞争并不激烈,则就是以轻量级锁状态来进行工作(自旋锁),能在第一时间拿到锁

如果当前锁竞争比较激烈,则就是以重量级锁状态来进行工作(挂起等待锁),拿到锁没有那么及时,但是节省了CPU的开销

而偏向锁是指必要的时候再加锁,能不加就不加,类似于懒汉模式,给某个线程加了锁之后,在实际执行的过程中并不一定就会真的触发锁竞争

 偏向锁并不是真加锁,只是设置了一个状态

举个例子:

我交了个女朋友A,考虑到未来换女朋友方便,就没有挑明关系,只行情侣之实,并无情侣之名(这就是以一个偏向锁状态)

如果在我和A交往的过程中,冒出来了个男生B,他也在接近A,对我产生了威胁,此时我立刻和A挑明了关系,并官宣,告诫B我是A的男朋友,让他离A远点(这个时候才是真加锁)

如果没有另外的男生B过来,我和A保持这种关系,直到我腻了之后直接和A分开(节省了 确立关系 & 分手 这些开销)

锁升级/锁膨胀:JVM实现synchronized的时候引入的一些优化机制

所以:

无竞争~>偏向锁

有竞争~>轻量级锁

竞争激烈~>重量级锁 

Ⅱ.锁消除

JVM自动判定,发现这个地方的代码,不必加锁,如果你写该处的时候加了锁,就会自动把锁去掉

例如:只有一个线程的时候  /  有多个线程的时候,并不涉及多个线程修改同一个变量,此时写了synchronized,那么它的加锁操作将会被JVM消除掉

synchronized刚开始的时候加的是偏向锁,只是修改标志位,开销并不大,但是能够消除的时候,就不必多这些开销

锁消除也是编译器优化的一种行为,编译器的判定不一定非常准,当判定代码中的锁100%能够消除,那么就会被消除掉,锁消除只是在编译器/JVM有100%把握的时候才会进行的

Ⅲ.锁粗化

锁的粒度:synchronized对应的代码块包含多少代码,包含的代码少,则粒度细;包含的代码多,则粒度粗

锁粗化就是把细粒度的加锁~>粗粒度的加锁

加锁是必要的,但反复的加锁解锁就会带来一些额外的锁竞争

举个例子:

在进行公司工作汇报的时候,是一个人接着一个人来汇报自己最近所做的工作,而不是这个工作汇报好先换别人汇报,然后自己再汇报其他的工作,所以就得把自己所要汇报的工作汇总起来,轮到自己汇报的时候全部工作一起完成汇报

 能够进行锁粗化的只能是对同一个对象加锁的才可以

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

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

相关文章

p2深度学习基本概念简介下笔记

3. 第 1 讲&#xff1a;深度学习基本概念简介下_哔哩哔哩_bilibili 线性模型太简单&#xff0c;我们需要比较复杂的模型。 不管怎样弄&#xff0c;蓝色描述不出红色的线 强调model bias 无法模拟真实的状况 写出一个有未知参数更复杂的model 观察红色的曲线 &#xff1a;可…

你知道数据在内存中是如何存储的嘛?

&#x1f996;作者&#xff1a;学写代码的恐龙 &#x1f996;博客主页&#xff1a;学写代码的恐龙博客主页 &#x1f996;专栏&#xff1a;【初级c语言】 &#x1f996;语录&#xff1a;❀未来的你&#xff0c;一定会感谢现在努力奋斗的自己❀ 深度剖析数据在内存中的存储一、…

数据结构精讲——排序(二)

数据结构精讲——排序&#xff08;二&#xff09; 排序的分类 上节课我们已经了解过插入排序和选择排序&#xff0c;这节课主要讲的是快排排序&#xff0c;冒泡排序应该都很熟悉&#xff0c;少提一下吧。 冒泡排序 冒泡排序是我们刚接触编程时就学习的排序方法&#xff08;还…

达梦数据库实施、运维与使用经验(一)

一、使用Xshell输入Insert语句&#xff0c;回车执行系统没反应 问题解决思路 查下数据库的活动会话数并登录数据库服务器看数据库的资源负载 select * from v$sessions where stateACTIVE;在数据库Xshell中输入语句查询数据库磁盘是否满了 df -lh上图中的第一个命令是用来查…

C++的IO流

目录 1. 流是什么 2. CIO流 2.1 C标准IO流 2.2 C文件IO流 2.2.1 简单读取文件流 2.2.2 C读取文件巧妙之处 3. stringstream的简单介绍 1. 流是什么 “流”即是流动的意思&#xff0c;是物质从一处向另一处流动的过程&#xff0c;是对一种有序连续且具有方向性的数据&am…

【我的渲染技术进阶之旅】关于OpenGL纹理压缩的相关资料

文章目录一、为啥要了解压缩纹理1.1 为啥要使用压缩纹理1.2 如何自定义压缩纹理以及使用压缩纹理的效果1.2.1 使用压缩纹理节省显存1.2.2 自定义压缩纹理&#xff1a;将压缩好的纹理数据保存在本地1.2.3 使用自定义的压缩纹理1.2.3 示例原理二、纹理压缩相关知识2.0 什么是压缩…

FOLOLane论文阅读

论文地址&#xff1a;https://arxiv.org/pdf/2105.13680.pdf 一、论文创新点 主流的车道线检测方法使用语义分割和聚类实现&#xff0c;但像素级的输出存在信息冗余&#xff0c;同时会带来大量噪声。而该论文使用两个分支&#xff0c;一个输出heatmap用于判断像素是否是关键点…

干货 | 初窥 Pytest 测试框架,基础薄弱也能轻松 hold 住

pytest 是一个成熟的全功能Python测试工具&#xff0c;可以帮助您编写更好的程序。它与 python 自带的 unittest 测试框架类似&#xff0c;但 pytest 使用起来更简洁和高效&#xff0c;并且兼容 unittest 框架。pytest 能够支持简单的单元测试和复杂的功能测试&#xff0c;pyte…

java中修改List的对象元素时碰到的坑

坑 case1 case1:Data class Person {Integer age;String name;public Person(Integer age, String name) {this.age age;this.name name;} } List<Person> v1List new ArrayList<>(); v1List.add(new Person(11, "小刚")); v1List.add(new Person(1…

前端基础(五)_运算符(算术运算符、赋值运算符、比较运算符、逻辑运算符、三目运算符、运算符优先级和结合性、数据类型的隐式转换)

一、算术运算符 算术运算符即&#xff1a;加&#xff08;&#xff09;减&#xff08;-&#xff09;乘&#xff08;*&#xff09;除&#xff08;/&#xff09;取余&#xff08;%&#xff09;加加&#xff08;&#xff09;减减&#xff08;–&#xff09; 算术运算符里比较特殊的…

CleanMyMacX软件有哪些优缺点?值不值得下载

CleanMyMac X 2023是一款可靠且功能强大的Mac清洁工具工具&#xff0c;他可以让你随时检查Mac电脑的健康情况&#xff0c;并删除电脑中的垃圾文件&#xff0c;来腾出存储空间&#xff0c;保持Mac系统的整洁。至问世以来&#xff0c;CleanMyMac 系统倍受国内外用户推崇&#xff…

【4】SCI易中期刊推荐——神经科学研究(中科院4区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…

C#使用迷宫地图来模拟新冠疫情的传播速度(一)

国家开始发布疫情放开政策&#xff0c;本人于2022-12-21开始感染并发高烧。 最近才康复。有感于此 我们用初始感染源来影响九宫网格来查看新冠的传播速度 小游戏规则如下&#xff1a; 一个感染源 可以传播附近相邻的8个网格【类似于扫雷】&#xff0c;假如每个感染源一天只…

Vue--》setup、ref、reactive函数使用讲解

目录 setup ref函数 reactive函数 Vue3中的响应式原理 setup Vue3中的一个新的配置项&#xff0c;值为一个函数。组件中所用到的数据、方法等等&#xff0c;均要配置在setup中。setup函数的两种返回值&#xff0c;如下&#xff1a; 若返回一个对象&#xff0c;则对象中的…

使用Docker快速搭建Hfish蜜罐

HFish简介 HFish是一款社区型免费蜜罐&#xff0c;侧重企业安全场景&#xff0c;从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发&#xff0c;为用户提供可独立操作且实用的功能&#xff0c;通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷感知和威胁情报领域的能…

源码讲解ThreadLocal父子线程通信问题(图+文+源码)

1 缘起 在复习ThreadLocal相关应用的知识&#xff0c; 有一个老生常谈的问题&#xff1a;父子线程通信&#xff0c; 起初&#xff0c;对于父子线程通信&#xff0c;仅了解ThreadLocal无法通过子线程获取线程数据&#xff0c; 并不了解为什么会这样&#xff1f;以及为什么Inher…

15.4 宏任务和微任务

宏任务和微任务 start 如果彻底理解了事件循环&#xff0c;其实大多数 JS 执行的逻辑都能理解了但是在 ES6 中引入了 Promise, 就引出了两个新概念&#xff0c;宏任务和微任务。 1.宏任务和微任务 1.1 名词 宏任务&#xff1a;macrotask 微任务&#xff1a;microtask 在 E…

异构操作系统的“融合计算”

这些年&#xff0c;由随着应用场景日益丰富和多样化&#xff0c;计算工作越来越复杂&#xff0c;传统的计算方式&#xff08;单机计算/分布式计算&#xff09;已经不能满足&#xff0c;需要一种新的更强大的计算模式来解决这些问题&#xff0c;这是融合计算产生的背景。 …

117.(leaflet之家)leaflet空间判断-点与geojson面图层的空间关系(turf实现)

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYPE html> <html>

Linux系统下的服务管理

文章目录Linux系统下的服务管理1.基本介绍2.service管理指令3.chkconfig指令4.systemctl管理指今4.1.基本语法4.2.systemctl设置服务的自启动状态4.3.防火墙实验案例4.4.防火墙Linux系统下的服务管理 1.基本介绍 服务(service) 本质就是进程&#xff0c;但是是运行在后台的&a…