CAS 和 Synchronized优化过程以及常见的锁策略

news2024/10/5 14:11:18

目录

🐑今日良言:追星赶月莫停留,平芜尽处是春山

🐂一、锁策略

🐼二、CAS

🐭三、Synchronized


🐑今日良言:追星赶月莫停留,平芜尽处是春山

🐂一、锁策略

锁策略是实现锁的时候,考虑出现锁竞争了该怎么办

1.乐观锁VS悲观锁
乐观锁:预测锁竞争不是很激烈(这里做的工作相对更少)
悲观锁:预测锁竞争很激烈(这里做的工作相对更多)
悲观和乐观唯一的区分:主要是看预测锁竞争激烈程度的结论
2.轻量级锁VS重量级锁
重量级锁:加锁机制重度依赖了OS提供mutex(互斥锁)
                大量的用户态和内核态切换  很容易引发线程的调度
轻量级锁:加锁机制尽可能不使用mutex,而是尽量在用户态用代码完成,实在搞不定了.再使用mutex
                 少量的内核态和用户态切换  不太容易引发线程调度
轻量级锁加锁解锁开销比较小,效率更高.
重量级锁加锁解锁开销比较大,效率更低.
3.自旋锁VS挂起等待锁
自旋锁:是一种典型的轻量级锁
挂起等待锁:是一种典型的重量级锁
自旋锁:获取锁失败,立即再次尝试获取锁,无线循环,一旦锁被释放,能第一时间感知到,从而获取到锁.   第一次获取锁失败,第二次尝试获取锁将在极短的时间内到来.
相较于挂起等待锁:
优点:没有释放CPU资源,一旦锁被释放,第一时间就能获取到,更高效,在锁持有时间比较短的场景中非常有用.
缺点:如果锁的持有时间较长,就会浪费CPU资源.
挂起等待锁:没有申请到锁的时候,线程被挂起,加入到阻塞队列中等待,当锁被释放后,有机会获取到锁.
4.互斥锁VS读写锁
互斥锁:就是synchronized这样的锁,提供了加锁解锁两个操作,如果一个线程加锁了,另一个线程也尝试加锁,就会阻塞等待
读写锁:提供了针对读加锁  针对写加锁  解锁  三种操作.
            读加锁和读加锁之间,不互斥
            读加锁和写加锁之间,互斥
            写加锁和写加锁之间,互斥
注:只要涉及到"互斥",就会产生线程的挂起等待.一旦挂起等待,再次唤醒就不知道隔了多久了.
     因此,尽可能减少"互斥"的机会,就是提高效率的重要途径.
读写锁适用于"频繁读 不频繁写"的场景中.
5.公平锁VS非公平锁
假设有三个线程A B C. A先尝试获取锁,获取成功.B尝试获取锁,获取失败,阻塞等待.C最后尝试获取锁,获取失败,阻塞等待,
当线程A释放锁以后,会发生什么呢?
公平锁:遵循"先来后到".B比C先尝试加锁,当A释放锁之后,B就能比C先获取到锁
非公平锁:不遵循"先来后到".B和C都可能获取到锁.
举个例子:
你和你的兄弟们一起追一个女生,当女生和自己的对象分手后,先追女生的男生先上位,这就是公平锁.
如果是女生不按照先后顺序,挑一个自己顺眼的,就是非公平锁
注:
   操作系统内部的线程调度可以视为是随机的,如果不做任何额外的限制,锁就是非公平锁
   如果要实现公平锁,就需要依赖额外的数据结构,来记录线程们的先后顺序.
   公平锁和非公平锁没有好坏之分,关键还是看适用场景
6.可重入锁VS不可冲入锁
可重入锁:一个线程针对一把锁加锁两次,不出现死锁.
不可冲入锁:一个线程针对一把锁加锁两次,出现死锁.

🐼二、CAS

 1.什么是CAS?

CAS全称: compare and swap. 根据字面意思理解为"比较并交换"

一个CAS涉及到以下操作:

假设内存中的原数据是V,旧的预期值是A,需要修改的新值是B

1).先比较A和V是否相等(比较)

2).如果比较相等,就将B写入V(交换)

3).返回操作是否成功

 在上述交换过程中,大多数情况下并不关心B后续的情况,更关心的是V这个变量的情况

这里说是交换,其实可以近似理解成:赋值

此处最特别的地方是:上述这个CAS过程并非是通过一段代码实现的,而是通过一条CPU指令完成的,这说明CAS操作是原子的!!!!!

 CAS可以理解成是CPU提供的一个特殊指令,通过这个指令可以一定程度上处理线程安全问题.

CAS的伪代码:

boolean CAS(value, oldValue, swapValue) {
 if (value == oldValue) {
   value = swapValue;
        return true;
   }
    return false;
}

2.CAS的应用场景

CAS的应用场景主要有两个:

1).实现原子类

     java标准库提供了java.util.concurrent.atomic包,里面的类都是基于CAS的方式实现的,其中最典型的就是AtomicInteger类,其中的getAndIncrement相当于i++操作

    通过下面一个练习来理解原子类:

    两个线程增加同一个变量

   

public class Exercise {
    public static void main(String[] args) throws InterruptedException {
       AtomicInteger integer = new AtomicInteger(0);
       Thread t1 = new Thread(() -> {
           for (int i = 0; i < 50000; i++) {
               integer.getAndIncrement();// 相当于i++操作
           }
       });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                integer.getAndIncrement();// 相当于i++操作
            }
        });
        t1.start();
        t2.start();
        // 为了让主线程等待t1线程和t2线程执行完毕
        t1.join();
        t2.join();
        System.out.println(integer);
    }
}

运行结果:

 

2).实现自旋锁

通过下面的伪代码理解:

public class SpinLock { 
    private Thread owner = null; 
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

 

 3.ABA问题

CAS运行的核心:检查value与oldValue是否一致.如果一致,就视为value中途没有被修改过,所以进行下一步交换操作是没问题的.

但是,这里的一致,可能是value没有被修改过,也有可能是value被修改过又改回来了. 

把value的值设为A的话,CAS判定value为A,此时value的值可能始终为A,也可能是value本来是A,被修改为了B,最后又还原成了A.

这就是CAS的典型问题:ABA问题

通过下面的实例来进一步理解ABA问题

假设 张三 有 100 存款 . 张三 想从 ATM 50 块钱 . 取款机创建了两个线程 , 并发的来执行 -50
操作 .
我们期望一个线程执行 -50 成功 , 另一个线程 -50 失败 .
如果使用 CAS 的方式来完成这个扣款过程就可能出现问题 .
正常的过程
1) 存款 100. 线程 1 获取到当前存款值为 100, 期望更新为 50; 线程 2 获取到当前存款值为 100,
望更新为 50.
2) 线程 1 执行扣款成功 , 存款被改成 50. 线程 2 阻塞等待中 .
3) 轮到线程 2 执行了 , 发现当前存款为 50, 和之前读到的 100 不相同 , 执行失败 .

 

异常的过程
1) 存款 100. 线程 1 获取到当前存款值为 100, 期望更新为 50; 线程 2 获取到当前存款值为 100,
望更新为 50.
2) 线程 1 执行扣款成功 , 存款被改成 50. 线程 2 阻塞等待中 .
3) 在线程 2 执行之前 ,张三 的朋友正好给张三转账 50, 账户余额变成 100 !!
4) 轮到线程 2 执行了 , 发现当前存款为 100, 和之前读到的 100 相同 , 再次执行扣款操作
这个时候 , 扣款操作被执行了两次 !!! 

 

 解决ABA问题的方法:给要修改的值,引入版本号

CAS 比较数据当前值和旧值的同时 , 也要比较版本号是否符合预期 .
CAS 操作在读取旧值的同时 , 也要读取版本号 .
真正修改的时候:
如果当前版本号和读到的版本号相同 , 则修改数据 , 并把版本号 + 1.
如果当前版本号高于读到的版本号 . 就操作失败 ( 认为数据已经被修改过了 ).
引入版本号后,解决刚才转账异常的情况:
假设 张三 有 100 存款 . 张三 想从 ATM 50 块钱 . 取款机创建了两个线程 , 并发的来执行 -50
操作 .
我们期望一个线程执行 -50 成功 , 另一个线程 -50 失败 .
为了解决 ABA 问题 , 给余额搭配一个版本号 , 初始设为 1.
1) 存款 100. 线程 1 获取到 存款值为 100, 版本号为 1, 期望更新为 50; 线程 2 获取到存款值为 100,
版本号为 1, 期望更新为 50.
2) 线程 1 执行扣款成功 , 存款被改成 50, 版本号改为 2. 线程 2 阻塞等待中 .
3) 在线程 2 执行之前 , 张三 的朋友正好给滑稽转账 50, 账户余额变成 100, 版本号变成 3.
4) 轮到线程 2 执行了 , 发现当前存款为 100, 和之前读到的 100 相同 , 但是当前版本号为 3, 之前读
到的版本号为 1, 版本小于当前版本 , 认为操作失败 .

 

 

🐭三、Synchronized

1).synchronized既是一个悲观锁,也是一个乐观锁

    synchronized默认是乐观锁,但是如果发现锁竞争激烈,就会变成悲观锁

2).synchronized既是一个轻量级锁,也是一个重量级锁

    synchronized默认是轻量级锁,人但是如果发现锁竞争激烈,就会变成重量级锁

3).synchronized这里的轻量级锁,是基于自旋锁的方式实现的.

    synchronized这里的重量级锁,是基于挂起等待锁的方式实现的.

4).synchronized 不是读写锁

5).synchronized 是非公平锁

6).synchronized 是可重入锁

synchronized内部有一些优化机制,存在的目的就是为了让这个锁更高效更好用.

1.加锁工作过程(锁升级/锁膨胀)

jvm将synchronized 锁分为:无锁 偏向锁 轻量级锁 重量级锁 状态,会根据情况,进行依次升级.

1).偏向锁

     第一个尝试加锁的线程,优先进入偏向锁状态.偏向锁不是真的"加锁",而是做个偏向锁标记,记录这个锁属于哪个线程.

    如果整个使用锁的过程中,没有出现锁竞争,在synchronized执行完之后,取消偏向锁即可

    如果整个使用锁的过程中.另一个线程也尝试加锁,那么在它加锁之前,迅速的把偏向锁状态升级为加锁状态,另一个线程只能阻塞等待了.

2).轻量级锁

     随着其它线程进入竞争,偏向锁状态被消除,进入轻量级锁状态(自适应的自旋锁)

     此处的轻量级锁就是通过CAS来实现的.

     自旋操作是一直让CPU空转,比较浪费CPU资源,因此,此处的自旋不会一直持续进行,而是达到一定的时间/重试次数,就不在自旋了,也就是所谓的"自适应".

3).重量级锁

    如果竞争进一步激烈,自旋不能快速获取到锁状态,就会升级为重量级锁

    此处的重量级锁就是指用到内核提供的mutex

2.锁消除

   编译器+JVM智能的判定:当前的代码是否真的需要加锁,如果这个场景不需要加锁,程序猿加了,就自动的把锁消除.

3.锁粗化

    一段逻辑中如果出现多次加锁解锁,编译器+JVM会自动进行锁的粗化.

 锁的粒度:synchronized包含的代码越多,粒度就越粗,包含的代码越少,粒度就越细.

 通常情况下,锁的粒度细一点比较好.

 加锁的部分代码,是不能并发执行的,锁的粒度越细,能并发执行的代码就越多,反之越少.

 但是,有的情况下,锁的粒度粗一些反而好,例如:两次加锁解锁之间间隙非常小,这种情况不如直接使用一次大锁搞定.

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

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

相关文章

电驱系统电磁兼容基础知识及测试方法

电驱系统电磁兼容基础知识及测试方法 学习参考&#xff1a;驱动视界公众号、百度百科、《电动汽车电机驱动系统EMC研究综述》 1.背景 2.电磁干扰三要素 3.电波暗室与屏蔽室的原理 4.测试方法 5.如何看测试数据 6.工作中需要注意的EMC的几点问题 7.案例 1.背景 汽车工业发展…

docker安装db2

第一步&#xff1a;下载镜像 docker pull ibmcom/db2express-c:latest备注&#xff1a;docker images -a 可以查看已安装镜像&#xff1b; 第二步&#xff1a;启动镜像 docker run -d --name db2 -p 50000:50000 -e DB2INST1_PASSWORD[数据库密码] -e LICENSEaccept ibmcom…

玩转PPT 第1节 PPT制作理念学习笔记

ppt神器islide 第1节 初步接触强大的工具1 PPT大神的课程总结1.1 骨架篇1.2 色彩篇1.3 对齐篇1.4 对比篇1.5 修饰篇1.6 字体篇1.7 素材篇1.8 线条篇1.8.1 可以随意画线条&#xff0c;填充空白1.8.2 在字体上画线条&#xff0c;做成艺术字1.8.3 做对称线条&#xff0c;比如递进三…

Bluesky勒索软件深度技术分析

0 1、 背景介绍 勒索软件作为一种流行的木马&#xff0c;近年来已成为最为常见的安全威胁之一。与其他威胁不同&#xff0c;勒索软件先向受害者表明自己的身份&#xff0c;再通过加密的方式使用户数据资产或计算资源无法正常使用&#xff0c;而恢复数据资产的唯一方法通常是支…

收藏破万,谷歌联手哈佛发布首个炼丹指南:教你科学化「调参」

调参不能只靠直觉&#xff0c;也是一门大学问&#xff01; 虽然算法工程师往往调侃自己是「调参侠」&#xff0c;但调参这件事可能真没想象中那么简单。 比如&#xff0c;你是不是经常会有疑惑到底该选择哪个优化器&#xff1f;一个batch放多少数据&#xff1f;超参数如何设置…

人工智能英文缩写怎么读,人工智能英文缩写大全

1、人工智能英文缩写是什么&#xff1f; AI。 人工智能&#xff0c;英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术以及应用系统的一门技术科学。“十三五”以来&#xff0c;我国高度重视人工智能的发展&#xff0c;国内科研机构也围绕人工智能…

【Pytorch项目实战】之机器翻译:编码器-解码器、注意力机制AM

文章目录机器翻译 - 中英文翻译算法一&#xff1a;编码器-解码器&#xff08;Encoder - Decoder&#xff09;算法二&#xff1a;注意力机制&#xff08;Attention Model&#xff0c;AM&#xff09;2.1为何要引入注意力机制&#xff1f;2.2注意力机制的语义向量表示C计算2.3每个…

2023年首更,警惕6本SCISSCI期刊被剔除

2023年1月17日&#xff0c;Web of Science核心期刊目录首次更新。此次SCIE & SSCI期刊目录更新&#xff0c;与上次更新&#xff08;2022年12月&#xff09;相比&#xff0c;共有6本期刊被剔除出SCIE & SSCI期刊目录&#xff0c;详情如下&#xff1a; 公众号“Unionpub…

Redis实战9-全局唯一ID

发布优惠券的时候&#xff0c;每个店铺都可以发布优惠券&#xff0c;当用户抢购的时候,优惠券表中的id如果使用数据库的自增长ID会存在以下问题&#xff1a; 1&#xff1a;id的规律太明显&#xff0c;容易被刷 2&#xff1a;当数据量很大的时候&#xff0c;会受到单表数据的限…

三、分布式id,lua脚本,分布式锁,消息队列

文章目录优惠卷秒杀1.redis实现分布式ID2.优惠券秒杀下单3.超卖问题4.lua脚本5.分布式锁6.redis stream消息队列实现异步秒杀7.redis消息队列list实现消息队列PubSub实现消息队列stream实现消息队列stream的消息队列-消费者组学习黑马点评项目整理总结:https://www.bilibili.co…

5 个用于自动化的杀手级 Python 脚本

Python 是一种功能强大的语言&#xff0c;广泛用于自动执行各种任务。无论您是开发人员、系统管理员&#xff0c;还是只是想通过自动化日常任务来节省时间的人&#xff0c;Python 都能满足您的需求。 这里有 5 个 Python 脚本&#xff0c;可以帮助您自动执行各种任务 文章目录…

Spring Boot之Swagger

文章目录零 项目源码一 Swagger简介二 Spring Boot集成Swagger2.1 环境准备2.2 配置Swagger2.3 配置文档信息2.4 配置扫描接口2.5 配置开关Swagger2.6 配置API分组2.7 配置实体类2.8 常用注解零 项目源码 Swagger源码免费下载 一 Swagger简介 Swagger官网swagger是一款可以根…

深入开源和金融技术世界,《新程序员005》来了!

亲爱的 CSDN 以及《新程序员》的读者朋友们&#xff0c;《新程序员005&#xff1a;开源深度指南 & 新金融背后的科技力量》正式与大家见面&#xff01;现在&#xff0c;点击下方封面&#xff0c;即可订阅&#xff0c;立享电子书&#xff0c;纸质书将在春节后为大家寄出&…

9、循环语句

目录 一、while循环语句 二、do…while循环语句 三、for循环语句 1. for语句 2. foreach语句 一、while循环语句 while语句也称条件判断语句&#xff0c;它的循环方式为利用一个条件来控制是否要继续反复执行这个语句。 语法如下&#xff1a; 当条件表达式的返回值为真时…

【并发编程十三】c++原子操作(1)

【并发编程十三】c原子操作&#xff08;1&#xff09;一、改动序列1、改动序列2、预测执行二、原子操作及其类别1、原子操作2、非原子操作3、原子类型三、标准原子类型1、标准原子类型的两种实现方式2、原子操作的用途3、原子操作的宏四、操作std:atomic_flag1、简介2、使用说明…

jsp ssm宿舍报修换宿管理系统-宿管idea

目 录 1 1绪论 1 1.1 课题研究的背景 1 1.2 课题研究的意义 1 1.3 本文主要工作 1 2 关键技术介绍 3 2.1 JSP技术的简介 3 2.2 SSM框架 3 2.3 MYSQL数据库 4 2.4 MySQL环境配置 4 2.5 B/S架构 4 3 需求分析与可行性分析 5 3.1功能需求分析…

年后公司缺人,面了13个测试员,发现他们都有一个通病.....

公司缺人&#xff0c;面了不少测试&#xff0c;结果竟然没有一个合适的。 一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资在10-20k&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。 看简历很多都是3年工作经验&#xff0c;但面试中…

数据不幸丢失怎么办?恢复丢失数据的 7 大方法

有时&#xff0c;您的系统可能会无意中被数据丢失等不可预见的事件唤醒。数据丢失已成为我们数字经济中的普遍现象&#xff0c;其发生的方式多种多样&#xff0c;包括意外删除、病毒攻击、无法访问的分区、数据损坏&#xff0c;甚至更灾难性的情况&#xff0c;如数据盗窃。 不…

Spring进阶:3步引入MybatisPlus多数据源,详细配置及原理解析

前言 MybatisPlus(MP)作为mybatis的增强工具&#xff0c;提供了配置多数据源的扩展&#xff0c;通过简单的几步配置&#xff0c;即可使用注解轻松切换数据源。 以下是dynamic-datasource提供的功能列表&#xff1a; 使用方法 1&#xff0c;引入dynamic-datasource-spring-bo…

第六章 数组、排序和查找

一、数组&#xff08;P156&#xff09; 1. 数组介绍 数组可以存放多个同一类型的数据。数组也是一种数据类型&#xff0c;是引用类型。即&#xff1a;数(数据)组(一组)就是一组数据2. 数组的使用 2.1 使用方式1&#xff1a;动态初始化 数组的定义&#xff1a;数据类型 …