CAS 和 synchronized 的优化过程

🍉 目录

CAS 的实现

CAS 的工作原理

优化过程

CAS 的应用

1) 实现原子类

2)实现自旋锁 

CAS 的 ABA 问题

synchronized 的 原理

synchronized 基本特点

加锁工作过程

其他优化操作 

1. 锁消除

2. 锁粗化 


CAS(Compare-And-Swap,即 比较和交换),是用于实现同步原语的一种原子操作。在Java的并发编程中,CAS 操作是轻量级和无锁算法的基础,它允许线程在不使用传统互斥锁的情况下安全地更新共享变量。以下是 CAS 优化的详细解释。

CAS 操作的引入主要是为了在多线程环境下提供一种高效、低开销的同步机制。通过避免使用重量级锁,CAS 操作可以减少线程的上下文切换和锁竞争带来的性能损失。

CAS 的实现

针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

1. Java的 CAS 利用的是 unsafe 这个类的提供的 CAS 操作

2. unsafe 的 CAS 依赖的是 JVM 针对不同的操作系统实现的 Atomic::cmpxchg

3. Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 CPU 硬件提供的 lock 机制保证其原子性。

简而言之,当硬件层面予以支持,软件层面才得以实现。

CAS 的工作原理

CAS 操作的基本思想是比较并交换。它包含三个参数:内存位置(V)、预期值(A)和新值(B)。CAS 操作会检查内存位置 V 的值,与预期值 A 是否相等,如果相等则将 V 替换为 B,否则不进行任何操作。CAS 操作是原子的,即它在硬件层面上是不可分割的,这确保了操作的线程安全性。

优化过程

🍉减少锁的使用

在许多情况下,CAS 操作可以替代传统的锁机制,从而避免锁带来的开销和竞争。通过使用 CAS 操作,线程可以在不阻塞的情况下尝试更新共享变量,这提高了系统的并发性能。

🍉自旋等待

当 CAS 操作失败时(即内存值与预期值不符时),即没有拿到锁对象时,线程也不会立即进入阻塞状态而是会开始自旋等待状态。在自旋等待期间线程会不断的重新尝试 CAS 操作,直到成功或者到某个自旋时间的阀阈值。这种自旋等待机制减少了线程上下文切换的开销,并提高了系统的相应速度。

🍉减少内存开销

CAS 操作通常只需要对少量的内存位置进行操作,这减少了内存带宽的消耗。相比之下,传统的锁机制需要维护一个复杂的等待队列和锁状态,这会增加内存开销。

🍉提高可扩展性

CAS操作是基于硬件原语的,因此它可以很好地扩展到多核处理器环境。在多核处理器上,CAS操作可以并行执行,而传统的锁机制可能需要跨核进行复杂的同步操作。

🍉避免死锁

由于CAS操作不涉及锁的持有和释放,因此它避免了死锁问题的发生。死锁是传统锁机制中常见的问题之一,它会导致线程永久性地阻塞在等待锁的状态下。

CAS 的应用

1) 实现原子类

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

AtomicInteger atomicInteger = new AtomicInteger ( 0 );
// 相当于 i++
atomicInteger.getAndIncrement();

 原子类伪代码

class AtomicInteger {
   private int value;
   public int getAndIncrement () {
      int oldValue = value;
      while ( CAS(value, oldValue, oldValue+ 1 ) != true ) {
              oldValue = value;
       }
            return oldValue;
    }
}

CAS 操作修改同一个变量是,直接读取内存而不是寄存器,修改也是直接修改的内存。是一条硬件指令,是原子的。

代码实现

public class Demo22 {public static AtomicInteger count=new AtomicInteger(0);//设置初始值public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i < 50000; i++) {count.getAndIncrement(); //操作是原子的}});Thread t2=new Thread(()->{for (int i = 0; i < 50000; i++) {count.getAndIncrement(); //操作是原子的}});//启动线程t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+count);}
}

结果显示

通过形如上述代码就可以实现一个原子类,不需要使用重量级锁,就可以高效完成自增操作。

2)实现自旋锁 

基于 CAS 实现更灵活的锁,获取到更多的控制权。

自旋伪代码

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 ;
          }
}

CAS 的 ABA 问题

 ABA 问题 :

假设存在两个线程 t1 和 t2 。如果有一个共享变量 num,初始值为A。

线程 t1 需要将原始 A 的值修改为 B(如果被其他线程修改了也没关系,一共修改为 B 只需要修改一次) ,在 t1 刚刚读取到 A 的值(value = A,oldvalue = A ),这时穿插了线程 t2 的执行,线程 t2 将 A 修改为 B 后,又将 B 修改为 A(value = A)。现在值被修改为 B 执行了一次,但是 t1 现在进行判断时,发现 value = oldvalue ,那么此时意味着 t1 也会修改 A 变为 B ,但是此时的修改是第二次,此时是第二次操作修改是错误的。(具体执行如下)

针对上面的 ABA 问题的解决方案

为了解决这个问题,可以使用版本号或时间戳来跟踪内存位置的变化。

针对上述修改值这个问题,我们引入一个版本号,每次判断 value 和 oldvalue 的时候也需要判断版本号,查看版本号是否和每次操作时读取的版本号一致。 在上面的 ABA 问题中引入版本号,当线程 t1 第一次读取的时候,版本号为1,后来经过 t2 的两次修改,虽然 num 的值变为了 A ,但是版本号不等于1,说明在 t1 未执行这段期间 t2 已经执行了(假设执行的就是 A 转变为了 B)。

如下图

synchronized 的 原理

synchronized 基本特点

1)开始为乐观锁,如果锁冲突频繁,转变为悲观锁

2)开始是轻量级锁实现,如果锁被持有的时间较长,转变为重要量级锁

3)实现轻量级锁的时候大概率需要用到自旋锁策略

4)synchronized 是一种可重入锁

5)synchronized 是一种不公平锁

6)synchronized 不是读写锁

加锁工作过程

🍉 偏向锁状态

工作原理:当只有一个线程(偏向线程)访问同步代码块或方法时,JVM会在对象的对象头中设置一个偏向锁标志,并将线程ID记录在对象头中。后续该线程再次访问时,只需检查对象头中的线程ID是否与其自身ID一致,若一致则无需进行任何同步操作,直接进入同步代码块。

 撤销与升级: 当有其他线程(竞争线程)尝试获取锁时,JVM会检测到偏向锁状态,并尝试撤销偏向锁,将锁升级为轻量级锁。

🍉 轻量级锁状态(轻量级锁是为了在线程交替执行同步块时提高性能而设计的)

随着其他线程进⼊竞争, 偏向锁状态被消除, 进⼊轻量级锁状态(⾃适应的⾃旋锁).
此处的轻量级锁就是通过 CAS 来实现.
通过 CAS 检查并更新⼀块内存 (⽐如 null => 该线程引⽤)
如果更新成功, 则认为加锁成功
如果更新失败, 则认为锁被占⽤, 继续⾃旋式的等待(并不放弃 CPU)
⾃旋操作是⼀直让 CPU 空转, ⽐较浪费 CPU 资源.
因此此处的⾃旋不会⼀直持续进⾏, ⽽是达到⼀定的时间/重试次数, 就不再⾃旋了.
也就是所谓的 "⾃适应"

     🚩自旋等待:自旋等待期间,线程会在一个小的循环中重复尝试获取锁,直到锁被释放或自旋次数超过阈值。

🍉 重量级锁状态(当锁竞争非常激烈,轻量级锁的自旋尝试无法快速获取锁时,JVM会将锁膨胀为重量级锁)

重量级锁使用操作系统提供的互斥量(mutex)机制来确保线程间的同步。线程会进入阻塞状态,并被放入等待队列(如Contention List Queue)中等待锁被释放。

      🚩锁释放与唤醒:当持有锁的线程执行完同步代码块并释放锁时,JVM会随机唤醒等待队列中的一个线程。

如下图:

其他优化操作 

JVM 根据配置和实现对 synchronized 锁的优化操作还有 锁消除、锁粗化。

1. 锁消除
编译器+ JVM  判断锁是否可消除,如果可以,就直接消除。什么意思呢???(一脸问号)
举个栗子:
StringBuffer sb = new StringBuffer ();
sb.append( "a" );
sb.append( "b" );
sb.append( "c" );
sb.append( "d" );
此时每个 append 的调⽤都会涉及加锁和解锁. 但如果只是在单线程中执⾏这个代码, 那么这些加锁解锁操作是没有必要的, ⽩⽩浪费了⼀些资源开销。
2. 锁粗化 
⼀段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会⾃动进⾏锁的粗化。
锁的粒度:粗和细
实际开发过程中, 使⽤细粒度锁, 是期望释放锁的时候其他线程能使⽤锁.
但是实际上可能并没有其他线程来抢占这个锁. 这种情况 JVM 就会⾃动把锁粗化, 避免频繁申请释放锁。

🚩文化篇:真光之人,压抑愈久,深潜愈甚,其光华之绽放乃愈灿烂也。 

以上就是本期的全部内容啦~希望对大家有帮助~~

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

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

相关文章

2024ICPC网络赛第一场

A 最终答案与中国队能力值的排名有关&#xff0c;具体每个情况手推一下&#xff0c;用 if else 即可通过。 #include <bits/stdc.h> using namespace std;int main() {ios::sync_with_stdio(false); cin.tie(0);int t, a[40];cin >> t;while (t--) {int num 0;f…

Arduino IDE离线配置第三方库文件-ESP32开发板

简洁版可以使用uget等&#xff0c;将文件下载到对应文件夹下&#xff0c;然后安装。 esp32之arduino配置下载提速 录屏 Arduino IDE离线配置第三方库文件ESP32 资源 Linux https://download.csdn.net/download/ZhangRelay/89749063 第三方开发板 非默认支持的开发板 linu…

Ubuntu24.04部署docker

1、更新软件 apt update 2、安装curl apt install apt-transport-https curl 3、导入阿里云GPG秘钥 curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 4、添加Docker阿里云仓库到Ubuntu 24.04的…

Python编码系列—Python适配器模式:无缝集成的桥梁

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

LLM - 理解 多模态大语言模型 (MLLM) 的指令微调与相关技术 (四)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/142063880 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 完备(F…

如何看待IBM中国研发部裁员

如何看待IBM中国研发部裁员&#xff1f; 近日&#xff0c;IBM中国宣布撤出在华两大研发中心&#xff0c;引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展&#xff0c;也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对…

文件格式转换:EXCEL和CSV文件格式互相转换

目录 1.EXCEl和CSV文件格式互相转换1.1首先安装所需的Python包1.2excel转换为csv代码如下&#xff1a;1.3csv转换为excel代码如下&#xff1a; 由于excel文件在数学建模数据处理当中的局限性&#xff0c;我们通常把excel文件转换为csv文件来处理&#xff0c;下面是相关的代码&a…

Java算法总结

文章目录 一、链表相关1.1 从尾到头打印单链表[要求 方式1&#xff1a;反向遍历。方式2&#xff1a;Stack栈]1.2 josephu问题&#xff08;使用带尾指针的循环链表&#xff09; 二、动态规划2.1 斐波那契数列 2022.4.182.2 青蛙上台阶 2022.4.18 三、位运算符3.1 二进制中1的个数…

统信服务器操作系统【d版字符系统升级到dde图形化】配置方法

统信服务器操作系统d版本上由字符系统升级到 dde 桌面系统的过程 文章目录 一、准备环境二、功能描述安装步骤1. lightdm 安装2. dde 安装 一、准备环境 适用版本&#xff1a;■UOS服务器操作系统d版 适用架构&#xff1a;■ARM64、AMD64、MIPS64 网络&#xff1a;连接互联网…

kettle 数据库迁移 使用分页原理实现 数据库mysql

使用 kettle 9.0 先修改配置文件: C:\Users\xx\.kettle 新增如下配置,解决mysql 空字符串 自动转 null bug KETTLE_EMPTY_STRING_DIFFERS_FROM_NULLY git地址: GitHub - 2292011451/kettle_tool 第一步: 先把要迁移的表进行读取,循环查询每个表的最大数量以及页数,追加到…

linux文件系统权限详解

注:目录的执行权限代表是否可以进入。 一、文件权限控制对文件的访问: 可以针对文件所属用户、所属组和其他用户可以设置不同的权限 权限具有优先级。user权限覆盖group权限,后者覆盖other权限。 有三种权限类别:读取、写入和执行 读权限:对文件:可读取文件…

VS Code 配置 C/C++ 编程运行环境(保姆级教程)

文章目录 一、软件下载1. 下载 VS Code 安装工具 2. 下载 MinGW-W64二、安装 VS Code三、安装 MinGW-W64 及配置环境变量四、配置 VS Code 的 C/C 编程运行环境1. 汉化 VS Code&#xff08;选做&#xff09;2. 安装 C/C 扩展包 五、测试 VS Code 的 C/C 编程环境1. 创建代码文件…

hku-mars雷达相机时间同步方案-软件驱动(MID360与海康MV-CB060-10UMUC-S)

hku-mars雷达相机时间同步方案-软件驱动 hku的方案在硬件和软件方面都做了工作&#xff0c;所以才会实现相机帧和雷达帧的完全对齐。硬件方面的设置请参考上一期。 港大的同步结果&#xff1a; hku-mars雷达相机时间同步方案-硬件&#xff08;MID360与海康MV-CB060-10UMUC-S&…

窗口嵌入桌面背景层(vb.net,高考倒计时特供版)

开发思路 根据系统生成高考倒计时的具体时间&#xff0c;附加江苏省省统考的时间生成算法&#xff0c;并且用户可以根据实际情况调整前后30天&#xff0c;具有丰富多彩的图片库和强大的自定义功能&#xff0c;效果图见P3 目前程序处于正式版的1.4版本&#xff0c;本程序由本作…

【HarmonyOS】鸿蒙头像上传-(编辑个人信息页- 头像上传)+实时数据更新

#效果图 #思路 ##步骤&#xff1a; ###一、利用picker api选择1张图片 实例化选择器参数(使用new PhotoSelectOptions())实例化图片选择器 (使用newPhotoViewPicker() )调用图片选择器的select方法传入选择器参数完成图片选取获得结果 利用picker api选择1张图片 async sele…

[Redis] Redis中的Hash类型和List类型

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

eureka.client.service-url.defaultZone的坑

错误的配置 eureka: client: service-url: default-zone: http://192.168.100.10:8080/eureka正确的配置 eureka: client: service-url: defaultZone: http://192.168.100.10:8080/eureka根据错误日志堆栈打断电调试 出现两个key&#xff0c;也就是defaultZone不支持snake-c…

【PCB工艺】如何实现PCB板层间的互连

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 前言①、什么是通孔②、通孔是怎样产生的③、通孔种类④、盘中孔⑤、设计建议 前言 送给大学毕业后找不到奋斗方向的你…

C++函数在库中的地址

本文讲述C如何直接调用动态库dll或者so中的函数。 首先我们准备一个被调用库&#xff0c;这个库里面有两个函数&#xff0c;分别是C98 与 C11 下的&#xff0c;名称是run2和run1。 被调用库 相关介绍请看之前的文章《函数指针与库之间的通信讲解》。 //dll_ex_im.h #ifndef…

OpenSSH9.8p1编译rpm包(建议收藏)

1.升级前的openssh版本 [root@ncayu8847 ~]# ssh -V OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 20172.下载软件包(离线包) openssh 源码下载地址: https://mirrors.aliyun.com/pub/OpenBSD/OpenSSH/portable/openssl源码下载 https:/