javaEE初阶——多线程(八)——常见的锁策略 以及 CAS机制

news2024/12/25 9:14:30

在这里插入图片描述

T04BF

👋专栏: 算法|JAVA|MySQL|C语言

🫵 小比特 大梦想

此篇文章与大家分享分治算法关于多线程进阶的章节——关于常见的锁策略以及CAS机制
如果有不足的或者错误的请您指出!

多线程进阶

1.常见的锁策略

我们需要了解的是,我们使用是锁,在加锁 / 解锁 / 遇到锁冲突的时候,都会怎么做

1.1乐观锁和悲观锁

指的是,在加锁的时候,遇到锁冲突的概率是大 还是 小,如果
如果预测当前出现锁冲突的概率大,那么说明后续要做的工作就会更多,加锁开销的时间就会更大,那么此时就是悲观锁
相反,如果预测当前出现锁冲突的概率小,那么说明后续要做的工作就会少一些,加锁开销的时间就会少一些,那么此时就是乐观锁
我们之前讲到的synchronized,本质上既是乐观锁又是悲观锁
指的是,对于synchronized来说,它是只是"自适应"的,在加锁的过程中,会自动统计出当前出现锁冲突的概率是大还是小
如果大,就会按照悲观锁的方式来执行(做的工作更多),此时往往是要通过内核来完成一些事情的
如果大,就会按照乐观锁的方式来执行(做的工作更少),此时往往是纯用户态的一些操作

1.2重量级锁 和 轻量级锁

一个锁是重量级锁还是轻量级锁,本质上取决于要做的工作的多与少
这样看来,实际上重量级锁和轻量级锁 与 悲观锁和乐观锁 的重叠度是很高的
这是因为,如果加锁过程中要做的事情少,那就是轻量级锁,加锁过程中要做的事情多,那就是重量级锁
而一般锁冲突概率高的时候,需要做的工作就多,低的时候就少
因此,按照我们上面所说,synchronized即使重量级锁又是轻量级锁

1.3自旋锁和挂起等待锁

自旋锁,是轻量级锁的一种典型实现方式
用伪代码来实现自旋锁就是

void lock () {
    while (true) {
      if(锁是否被占用) {
         continue;
      }
      获取到锁
      break;
    }
}

自旋锁的功能就是,当一个线程尝试获取锁的时候,如果获取锁失败了,那么说明此时的锁正在被占用,出现了锁冲突,就会让当前这个线程进行一段自旋操作,即在循环中不断地尝试获取锁,而不会立即进入阻塞状态。
但是这种方式消耗了大量的系统资源,大部分处于忙等状态,但一旦锁被释放,就能立即获取到锁(拿到锁的速度更快了,但是消耗cpu)

而挂起等待锁是重量级锁的一种典型实现方式,实际上是借助系统的线程调度机制,当一个线程尝试获取锁,并且锁被占用了,出现了锁冲突,就会让当前这个线程挂起等待(阻塞状态),就不去参与调度了
直到锁被释放了,然后系统才去唤醒这个线程,去尝试重新获取锁
此时消耗的时间更长,一旦线程被阻塞了,即使锁释放了,什么时候唤醒,都是不可控的,可能会消耗很长的时间(拿到锁的速度慢了,节省cpu)

在java中,synchronized的轻量级锁部分是基于自旋锁实现的,而重量级锁是基于挂起等待锁实现的

1.4可重入锁和不可重入锁

这一点我们在介绍死锁的文章有提过
在java中,synchronized就是可重入锁

1.5公平锁和非公平锁

公平锁本质上就是严格按照线程的先来后到顺序来获取锁,哪个线程等待的时间长,哪个线程就先拿到锁
而在非公平锁中,谁先获取到锁,是随机的,和线程调度时间就无关了

而java里面的synchronized就是非公平锁,即多个线程尝试获取同一把锁,此时是按照概率均等的方式来获取的

这是由于系统本事线程调度就是随机的,如果要实现公平锁,那么就要引入额外的队列,按照加锁的顺序把这些获取锁的线程入队列,再一个一个的获取

1.6互斥锁和读写锁

synchronized本身就是一个互斥锁,而读写锁则是更加特殊的一种锁
对于互斥锁,本质上就两步操作,加锁和解锁
而读写锁要区分加读锁和加写锁
就是要实现,不同线程之间,读和读不会产生互斥

在日常开发中,有很多场景都是属于"读多 写少",如果使用普通的互斥锁,此时,每次读操作之间,即使不会产生线程安全问题,也会互斥,此时就会比较影响效率

1.7synchronized的自适应过程

按照我们上面所说的几个锁策略,synchronized是"乐观锁和悲观锁",“轻量级锁和重量级锁”,“轻量级锁部分是基于自旋锁实现的,重量级锁部分是基于挂起等待锁实现的”,“不可重入锁”,“非公平锁”,“互斥锁”

synchronized的自适应过程就是
+未加锁的状态 -----(开始执行synchronized) —> 偏向锁 -----遇到锁冲突----> 轻量级锁 ------锁冲突概率进一步提升 ----> 重量级锁

关于偏向锁
指的是,实际上我们使用synchronized进行加锁的时候,一开始不是真正加锁了,而只是做了一个标记,非常轻量,几乎没有开销
此时,如果没有别的线程针对同一个锁对象进行加锁,那么就会一直保持这个状态,直到解锁
但是如果在偏向锁的情况下,发现有别的线程针对同一个锁对象进行加锁,就立马把偏向锁升级为轻量级锁,此时就是真正加锁了,就会产生互斥了

本质上,偏向锁的思想就是"懒"的体现,能不加锁就不加锁,能晚加锁就晚加锁,就能省下很多锁的开销
注意:上述锁的升级过程是不可逆的

1.8锁消除

指的是,如果你的代码里面出现加锁操作,此时编译器就会自动帮你判断,当前这里是不是真的要进行加锁,如果不是,就会自动帮你把锁给优化掉
最典型的就是,当你在单线程里进行加锁操作

1.9锁粗化

本质上与锁的"粒度"有关
在加锁的范围内,包含的代码越多,就认为锁的粒度越粗,反之越细

锁粗化实际上也是一种优化策略,有的时候,需要频繁加锁解锁,此时编译器就会自动把多次细粒度的锁,合并成一次粗粒度的锁

2.CAS机制

所谓CAS,就是compare and swap,即比较和交换
指的是通过 一条cpu指令,就能完成 比较和交换 这样一套操作,即"原子的"

2.1CAS的流程

我们可以将他想象成一个方法

boolean CAS(address,reg1,reg2){
    if(*address == *reg1){
        将address里面的值和reg2寄存器里面的值进行交换
        return true;
    }
    return flase;
}

而我们上述模拟的这套操作,是通过一条cpu指令来完成的
实际上上述说的交换,我们通常是用来表示"赋值",因为实际上我们并不关心寄存器里面存的是什么值,而更加关心交换到内存里面的值是多少

因此上述操作也可以近似认为是将寄存器里面的值赋值到内存中

2.2CAS的使用

由于cpu提供了这样的指令,那么操作系统就会对应提供执行这样的指令的API,而JVM又对这样的API进行了封装,那么在java代码中也就直接使用CAS操作了

但是在java里面,CAS相关的类是被封装在unsafe包里面的,使用这里面的东西容易出错,就不鼓励直接使用CAS
在java中,又有一些类是对CAS进行了进一步的封装,典型的就是"原子类"
在这里插入图片描述
举其中的一个例子,我们上面圈出来的AtomicInteger
就相当于针对int进行了封装,就可以保证此处的++,–等操作操作是原子的了
我们在之前写过一个存在线程安全问题的代码
在这里插入图片描述
而当时我们的解决策略就是进行加锁,进行加锁,就会触发阻塞等待,只要代码有了加锁,就基本是与高性能无缘了
实际上这里用CAS会更好
在这里插入图片描述
可以发现,此时就不能简单的对count进行++/–操作了,而正确的操作是:

               count.getAndIncrement();//后置++
               count.getAndDecrement();//后置--
               count.decrementAndGet();//前置--
               count.incrementAndGet();//前置++
               count.getAndAdd(10);//count += 10

那么由于此时是原子操作,线程就是安全的了
在这里插入图片描述
此处我们的代码中就不涉及任何的加锁操作了

使此时的代码以更高的效率来执行程序

这一套基于CAS,不加锁来实现线程安全代码的方式,也称为"无锁编程"

虽然CAS机制挺好的,但是实际上使用范围没有锁广泛,只能是针对一些特殊场景,使用CAS是更高效的,但是有些场景是不适合使用CAS的

2.3基于CAS实现自旋锁

在java中,synchronized的自旋锁,就是基于CAS实现的
我们通过伪代码来理解自旋锁的实现

public class SpinLock {
   private Thread owner = null;//表示当前持有锁的线程是谁,如果此时处于未加锁状态,那owner就是null
   
   public void lock() {
     //通过CAS查看当前的锁对象是否已经被获取,如果是,那就要自旋等待
     //如果不是,那就把owner设置为当前尝试进行加锁的线程,此时cas就返回true,再取反,就是false,循环结束
     while(!CAS(this.owner,null,Thread.currentThread())) {
     }
   }
  public void unLock() {
    this.owner = null;
  }
}

而此处的比较和赋值就是原子的,就可以借助这样的逻辑来进行加锁的实现
ABA问题是属于CAS的一个重要注意事项

2.4CAS的ABA问题

我们前面说过,CAS机制的核心就是"比较 -> 发现相等 -> 交换",即检查当前内存里的值是否被其他线程修改,如果被修改了,就要稍后重试,如果没被修改,接下来就可以修改(不会有安全问题)

但是发现相等的潜台词就是 中间数据没有变化过

但是事实上,在中间可能会有其他线程将这个数从 A - B - A

那么此时就会出现 看起来好像没人修改,实际上已经被改过了的情况

而CAS机制是无法区分当前这个数据是确实没被修改,还是被修改了又改回来

虽然大部分情况下是没有影响的

但是在极端情况下会出现bug

举个例子就是取款场景

假设我们的取款操作是这样类似这样的

在这里插入图片描述

但是有可能发生就是某个人在一次取款操作中多按了一次,导致产生了两个线程来执行上述的扣款操作,那么假设我们的执行流程是:

在这里插入图片描述

那么此时t1线程什么都不执行,什么都不执行是对的!

但是如果此时恰好有第3个人线程给这个人的账户里面转了500,那么就会出问题了

在这里插入图片描述

那么此时t1就又扣款一次,导致取的是500.实际上扣的是1000

上述场景就是典型的ABA问题,实际上是很极端的情况

用户要点击两次 && 恰好在这个时候有人给你转了500 && 恰好转账金额是500

诸多的巧合,哪一环扣不上都不会出现上述的问题

如何解决ABA问题??

核心思路就是,使用账户余额作为判定条件本身就不太合理,因为账户余额本身就属于"能加也能减",就容易出现ABA问题

合理的是,我们可以引入"版本号".约定版本号只能加,不能减,每次操作一遍余额,版本号就+1,通过CAS判定版本号是否与当前版本号相同,如果是就可以进行下一步操作,如果版本号没变过,那么数据就一定没变过

在这里插入图片描述

感谢您的访问!!期待您的关注!!!

在这里插入图片描述

T04BF

🫵 小比特 大梦想

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

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

相关文章

【软考】UML中的关系

目录 1. 说明2. 依赖3. 关联4. 泛化5. 实现 1. 说明 1.UML中有4种关系:依赖、关联、泛化和实现2.这 4种关系是 UML,模型中可以包含的基本关系事物。它们也有变体,例如,依赖的变体有精化、跟踪、包含和延伸 2. 依赖 1.依赖(Dependency)。2.…

文心一言 VS 讯飞星火 VS chatgpt (242)-- 算法导论17.4 1题

一、假定我们希望实现一个动态的开地址散列表。为什么我们需要当装载因子达到一个严格小于 1 的值 a 时就认为表满?简要描述如何为动态开地址散列表设计一个插入算法,使得每个插入操作的摊还代价的期望值为 O(1) 。为什么每个插入操作的实际代价的期望值…

Excel如何计算时间差

HOUR(B1-A1)&"小时 "&MINUTE(B1-A1)&"分钟 "&SECOND(B1-A1)&"秒"

下级平台级联EasyCVR视频汇聚安防监控平台后,设备显示层级并存在重复的原因排查和解决

视频汇聚平台/视频监控系统/国标GB28181协议EasyCVR安防平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力,平台支持7*24小时实时高清视频监控,能同时…

机器学习-10-基于paddle实现神经网络

文章目录 总结参考本门课程的目标机器学习定义第一步:数据准备第二步:定义网络第三步:训练网络第四步:测试训练好的网络 总结 本系列是机器学习课程的系列课程,主要介绍基于paddle实现神经网络。 参考 MNIST 训练_副…

深入剖析机器学习领域的璀璨明珠——支持向量机算法

在机器学习的广袤星空中,支持向量机(Support Vector Machine,简称SVM)无疑是一颗璀璨的明珠。它以其独特的分类能力和强大的泛化性能,在数据分类、模式识别、回归分析等领域大放异彩。本文将详细剖析SVM算法的原理、特…

MLLM | InternLM-XComposer2-4KHD: 支持336 像素到 4K 高清的分辨率的大视觉语言模型

上海AI Lab,香港中文大学等 论文标题:InternLM-XComposer2-4KHD: A Pioneering Large Vision-Language Model Handling Resolutions from 336 Pixels to 4K HD 论文地址:https://arxiv.org/abs/2404.06512 Code and models are publicly available at https://gi…

互联网扭蛋机小程序:打破传统扭蛋机的局限,提高销量

扭蛋机作为一种适合全年龄层的娱乐消费方式,深受人们的喜欢,通过一个具有神秘性的商品给大家带来欢乐。近几年,扭蛋机在我国的发展非常迅速,市场规模在不断上升。 经过市场的发展,淘宝线上扭蛋机小程序开始流行起来。…

一文讲透彻Redis 持久化

文章目录 ⛄1.RDB持久化🪂🪂1.1.执行时机🪂🪂1.2.RDB原理🪂🪂1.3.小结 ⛄2.AOF持久化🪂🪂2.1.AOF原理🪂🪂2.2.AOF配置🪂🪂2.3.AOF文件…

40+ Node.js 常见面试问题 [2024]

今天就开始你的Node.js生涯。在这里,我们探讨了最佳Node.js面试问题和答案,以帮助应届生和经验丰富的候选人获得理想的工作。 Node.js 是许多大公司技术堆栈的重要组成部分,例如 PayPal、Trello、沃尔玛和 NASA。 根据 ZipRecruiter 的数据&…

了解边缘计算,在制造行业使用边缘计算。

边缘计算是一种工业元宇宙技术,可以帮助组织实现其数据的全部潜力。 处理公司的所有数据可能具有挑战性,而边缘计算可以帮助公司更快地处理数据。在制造业中,边缘计算可以帮助进行预测性维护和自动驾驶汽车操作等工作。 什么是边缘计算? …

ruoyi-cloud-plus添加一个不要认证的公开新页面

文章目录 一、前端1. 组件创建2. src/router/index.ts3. src/permission.ts 二、后端1. 设计思想2. ruoyi-gateway.yml3. 开发Controller 版本RuoYiCloudPlusv2.1.2plus-uiVue3 ts 以新增一个公开的课程搜索页面为例。 一、前端 1. 组件创建 在view目录下创建一个页面的vue…

python--使用pika库操作rabbitmq实现需求

Author: wencoo Blog:https://wencoo.blog.csdn.net/ Date: 22/04/2024 Email: jianwen056aliyun.com Wechat:wencoo824 QQ:1419440391 Details:文章目录 目录正文 或 背景pika链接mqpika指定消费数量pika自动消费实现pika获取队列任务数量pi…

去哪儿网开源的一个对应用透明,无侵入的Java应用诊断工具

今天 V 哥给大家带来一款开源工具Bistoury,Bistoury 是去哪儿网开源的一个对应用透明,无侵入的java应用诊断工具,用于提升开发人员的诊断效率和能力。 Bistoury 的目标是一站式java应用诊断解决方案,让开发人员无需登录机器或修改…

使用大卫的k8s监控面板(k8s+prometheus+grafana)

问题 书接上回,对EKS(AWS云k8s)启用AMP(AWS云Prometheus)监控AMG(AWS云 grafana),上次我们只是配通了EKSAMPAMG的监控路径。这次使用一位大卫老师的grafana的面板,具体地址如下: ht…

Google Ads广告为Demand Gen推出生成式AI工具,可自动生成广告图片

谷歌今天宣布在Google Ads广告中为Demand Gen活动推出新的生成人工智能功能。 这些工具由谷歌人工智能提供支持,广告商只需几个步骤即可使用文本提示创建高质量的图片。 这些由人工智能驱动的创意功能旨在增强视觉叙事能力,帮助品牌在YouTube、YouTube…

【Hadoop】-Apache Hive概述 Hive架构[11]

目录 Apache Hive概述 一、分布式SQL计算-Hive 二、为什么使用Hive Hive架构 一、Hive组件 Apache Hive概述 Apache Hive是一个在Hadoop上构建的数据仓库基础设施,它提供了一个SQL-Like查询语言来分析和查询大规模的数据集。Hive将结构化查询语言(…

第十二届蓝桥杯C/C++ B组 杨辉三角形(二分查找+思维)

3418. 杨辉三角形 - AcWing题库 题目描述: 思路: 从上图片中,我们可以看出来这是一个对称图形,所以我们只看左半部分就可以了,我们一行一列去做数据量是1e9这样会很麻烦,所以我们这里做一个思想转换,斜着…

单片机 VS 嵌入式LInux (学习方法)

linux 嵌入式开发岗位需要掌握Linux的主要原因之一是,许多嵌入式系统正在向更复杂、更功能丰富的方向发展,需要更强大的操作系统支持。而Linux作为开源、稳定且灵活的操作系统,已经成为许多嵌入式系统的首选。以下是为什么嵌入式开发岗位通常…

申请IP地址SSL证书的七大步骤

申请IP地址SSL证书的目的是为了在使用IP地址作为访问地址而非域名的情况下,为您的服务提供HTTPS加密,确保数据传输的安全性。以下是申请IP地址SSL证书的一般步骤和注意事项: 一、选择合适的SSL证书类型: IP SSL证书:…