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

在这里插入图片描述

T04BF

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

🫵 小比特 大梦想

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

目录

  • 多线程进阶
    • 1.常见的锁策略
      • 1.1乐观锁和悲观锁
        • 1.2重量级锁 和 轻量级锁
        • 1.3自旋锁和挂起等待锁
        • 1.4可重入锁和不可重入锁
        • 1.5公平锁和非公平锁
        • 1.6互斥锁和读写锁
        • 1.7synchronized的自适应过程
        • 1.8锁消除
        • 1.9锁粗化
    • 2.CAS机制
      • 2.1CAS的流程
      • 2.2CAS的使用
      • 2.3基于CAS实现自旋锁
      • 2.4CAS的ABA问题
          • 如何解决ABA问题??

多线程进阶

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就是nullpublic 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.rhkb.cn/news/313771.html

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

相关文章

Pytorch 学习路程 - 1:入门

目录 下载Pytorch 入门尝试 几种常见的Tensor Scalar Vector Matrix AutoGrad机制 线性回归尝试 使用hub模块 Pytorch是重要的人工智能深度学习框架。既然已经点进来,我们就详细的介绍一下啥是Pytorch PyTorch 希望将其代替 Numpy 来利用 GPUs 的威力&…

云赛道---AI开发框架

MindSpore 旨在提供端边云全场景的 AI 框架。 MindSpore 可部署于端、边、云不同的 硬件环境,满足不同环境的差异化需求,如支持端侧的轻量化部署,支持云侧丰富的 训练功能如自动微分、混合精度、模型易用编程等。 MindSpore 全场景的几个重…

IIR滤波器的设计与实现(内含设计IIR滤波器的高效方法)

写在前面:初学者学习这部分内容,要直接上手写代码可能会感到比较困难,我这里推荐一种高效快速的设计IIR,FIR滤波器的方法——MATLAB工具箱:filterDesigner。打开的方法很简单,就是在命令行键入:filterDesig…

Visual Studio安装MFC开发组件

MFC由于比较古老了,Visual Studio默认没有这个开发组件。最近由于一些原因,需要使用这个库,这就需要另外安装。 参考了网上的一些资料,根据实际使用,其实很多步骤不是必须的。 https://zhuanlan.zhihu.com/p/68117276…

TypeScript 装饰器

​🌈个人主页:前端青山 🔥系列专栏:React篇 🔖人终将被年少不可得之物困其一生 依旧青山,本期给大家带来React篇专栏内容:TypeScript 装饰器 目录 一、是什么 二、使用方式 类装饰 方法/属性装饰 参数装饰 访问器…

Objective-C网络数据捕获:使用MWFeedParser库下载Stack Overflow示例

概述 Objective-C开发中,网络数据捕获是一项常见而关键的任务,特别是在处理像RSS源这样的实时网络数据流时。MWFeedParser库作为一个优秀的解析工具,提供了简洁而强大的解决方案。本文将深入介绍如何利用MWFeedParser库,以高效、…

深度学习系列64:数字人wav2lip详解

1. 整体流程 第一步,加载视频/图片和音频/tts。用melspectrogram将wav文件拆分成mel_chunks。 第二步,调用face_detect模型,给出人脸检测结果(可以改造成从文件中读取),包装成4个数组batch:img…

ExcelVBA把当前工作表导出为PDF文档

我们先问问Kimi Excel导出为PDF的方法有多种,以下是一些常见的方法: 1 使用Excel软件的内置功能: 打开Excel文件,点击“文件”菜单。 选择“另存为”,在“保存类型”中选择“PDF”。 设置保存路径和文件名&#xff…

transformer 最简单学习3, 训练文本数据输入的形式

1、输入数据中,源数据和目标数据的定义 def get_batch(source,i):用于获取每个批数据合理大小的源数据和目标数据参数source 是通过batchfy 得到的划分batch个 ,的所有数据,并且转置列表示i第几个batchbptt 15 #超参数,一次输入多少个ba…

GPU深度学习环境搭建:Win10+CUDA 11.7+Pytorch1.13.1+Anaconda3+python3.10.9

1. 查看显卡驱动及对应cuda版本关系 1.1 显卡驱动和cuda版本信息查看方法 在命令行中输入【nvidia-smi】可以当前显卡驱动版本和cuda版本。 根据显示,显卡驱动版本为:Driver Version: 516.59,CUDA 的版本为:CUDA Version 11.7。 此处我们可以根据下面的表1 显卡驱动和c…

iText生成PDF文件

导语: 本文基于 iText7 :7.1.16 生成。 官方文档链接:iText 从版本入口可进入到下面页面 一、引言 常见生成PDF文件的有两种方法,一是先生成 word文档,然后将word转换成PDF文件;另一种则是直接生成PDF文件…

改变 centos yum源 repo

centos 使用自带的 repo 源 速度慢,可以改为国内的,需要改两个地方 centos7.repo CentOS-Base.repo 首先备份/etc/yum.repos.d/CentOS-Base.repo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup下载对应版本repo文件…

NameNode锁细粒度优化在B站的实践

1. 背景 随着业务的高速发展,针对HDFS元数据的访问请求量呈指数级上升。在之前的工作中,我们已经通过引入HDFS Federation和Router机制实现NameNode的平行扩容,在一定程度上满足了元数据的扩容需求;也通过引入Observer NameNode读…

Quarto Dashboards 教程 2:Dashboard Layout

「写在前面」 学习一个软件最好的方法就是啃它的官方文档。本着自己学习、分享他人的态度,分享官方文档的中文教程。软件可能随时更新,建议配合官方文档一起阅读。推荐先按顺序阅读往期内容: 1.quarto 教程 1:Hello, Quarto 2.qu…

C语言扫雷游戏完整实现(上)

文章目录 前言一、新建好头文件和源文件二、实现游戏菜单选择功能三、定义游戏函数四、初始化棋盘五、 打印棋盘函数六、布置雷函数七、玩家排雷菜单八、标记功能的菜单九、标记功能菜单的实现总结 前言 C语言从新建文件到游戏菜单,游戏函数,初始化棋盘…

【C语言】深入解析选择排序算法

一、算法原理二、算法性能分析三、C语言实现示例四、总结 一、算法原理 选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理是不断地选择剩余元素中的最小(或最大)元素,放到已排序的序列的末尾&#xff…

普乐蛙VR航天航空体验馆VR双人旋转座椅元宇宙VR飞船

多长假来袭!!想为门店寻找更多新鲜有趣的吸粉体验?想丰富景区体验?别着急,小编为你准备了一款爆款设备——时光穿梭机,720无死角旋转!!吸睛、刺激体验,将亲子、闺蜜、情侣…

微信小程序酒店选择日期和入住人数(有效果图)

效果图 app.vue onLaunch:function(options){this.defaultcache()}defaultcache(){// 入住信息缓存var arr this.getDateTime();var ReserVation {reservType:0,//1 人数 2日期InCheckin:{},//入离日期peopleArr:[{title:成人,num:2},{title:儿童,num:0},{title:宝子,num:1…

宁盾LDAP统一用户认证与单点登录:构建高效安全的企业身份认证

在信息化时代,企业面临着众多的应用系统和数据资源,如何有效地管理和保护这些资源,确保信息安全和高效利用,成为了企业信息化建设的核心问题。LDAP统一用户认证和单点登录(SSO)作为一种高效、安全的身份验证…

全开源小狐狸Ai系统 小狐狸ai付费创作系统 ChatGPT智能机器人2.7.6免授权版

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 测试环境:Linux系统CentOS7.6、宝塔、PHP7.4、MySQL5.6,根目录public,伪静态thinkPHP,开启ssl证书 具有文章改写、广告营销文案、编程…