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.mzph.cn/diannao/3641.shtml

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

相关文章

【大数据】分布式数据库HBase

目录 1.概述 1.1.前言 1.2.数据模型 1.3.列式存储的优势 2.实现原理 2.1.region 2.2.LSM树 2.3.完整读写过程 2.4.master的作用 1.概述 1.1.前言 本文式作者大数据系列专栏中的一篇文章,按照专栏来阅读,循序渐进能更好的理解,专栏…

JS实现对用户名、密码进行正则表达式判断,按钮绑定多个事件,网页跳转

目标&#xff1a;使用JS实现对用户名和密码进行正则表达式判断&#xff0c;用户名和密码正确时&#xff0c;进行网页跳转。 用户名、密码的正则表达式检验 HTML代码&#xff1a; <button type"submit" id"login-btn" /*onclick"login();alidate…

精益思想赋能数字化转型:落地策略与实践路径

当下&#xff0c;数字化转型已不再是选择题&#xff0c;而是关乎企业生存与发展的必答题。然而&#xff0c;转型过程中如何确保效率、降低成本并快速实现价值创造&#xff0c;成为了摆在众多企业面前的难题。精益思想作为一种追求精益求精、持续改进的管理思维&#xff0c;为数…

2024最新版JavaScript逆向爬虫教程-------基础篇之面向对象

目录 一、概念二、对象的创建和操作2.1 JavaScript创建对象的方式2.2 对象属性操作的控制2.3 理解JavaScript创建对象2.3.1 工厂模式2.3.2 构造函数2.3.3 原型构造函数 三、继承3.1 通过原型链实现继承3.2 借用构造函数实现继承3.3 寄生组合式继承3.3.1 对象的原型式继承3.3.2 …

stm32HAL库-GPIO

一 什么是 GPIO: GPIO(general porpose intput output), 通用输入输出端口 . 二 我们先认识芯片控制 GPIO 输出控制。 2.1LED 硬件原理如图&#xff1a; 当电流从这根电线流通&#xff0c; LED 亮。当电流不通过这根电线&#xff0c; LED 灭。 上面 PF** &#xff0c;芯片电…

MySQL面试——聚簇/非聚簇索引

存储引擎是针对表结构&#xff0c;不是数据库 引擎层&#xff1a;对数据层以何种方式进行组织 update&#xff1a;加索引&#xff1a;行级锁&#xff1b;不加索引&#xff1a;表级锁

固态继电器:推进可再生能源系统

随着可再生能源系统的发展&#xff0c;太阳能系统日益成为现代能源解决方案的先锋。在这种背景下&#xff0c;固态继电器&#xff08;SSR&#xff09;&#xff0c;特别是光耦固态继电器的利用变得日益突出。本文旨在深入探讨SSR在可再生能源系统中的多方位应用&#xff0c;重点…

【学习笔记】Python 使用 matplotlib 画图

文章目录 安装中文显示折线图、点线图柱状图、堆积柱状图坐标轴断点参考资料 本文将介绍如何使用 Python 的 matplotlib 库画图&#xff0c;记录一些常用的画图 demo 代码 安装 # 建议先切换到虚拟环境中 pip install matplotlib中文显示 新版的 matplotlib 已经支持字体回退…

SD-WAN:灵活、低成本、便于管理

近年来&#xff0c;SD-WAN&#xff08;软件定义广域网&#xff09;技术成为企业网络领域的新趋势&#xff0c;其带来的变革性影响备受瞩目。凭借出色的灵活性、高效的可管理性以及显著的成本优势&#xff0c;SD-WAN技术为企业网络注入了新的活力。 首先&#xff0c;SD-WAN技术的…

如何利用diskpart命令界面在win10/win11上解除U盘写保护

背景 在把U盘作为系统盘装了一次后&#xff0c;惊讶的发现自己U盘的一个1M的小卷被写保护了。不能格式化&#xff0c;不能删除文件&#xff0c;在给用户拷文件的时候&#xff0c;小卷还会提示病毒告警&#xff0c;非常的尴尬&#xff0c;因此展开了研究。 失败的尝试 尝试了网…

58、回溯-组合总和

思路&#xff1a; 数组内的每一个元素都可以无线使用只要最后可以拼接成target就可以。那么如何限制呢&#xff1f; &#xff08;target-已经拼接的和 &#xff09;/当前元素 就是你可以利用的数量。代码如下&#xff1a; class Solution {public static List<List<I…

触发器的基本概念及分类

目录 触发器的基本概念 作用对象 触发事件 触发条件 触发时间 触发级别或者触发频率 触发器的分类 DML 触发器 INSTEAD OF 触发器 系统触发器 Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 触发器的基本概念 …

2024年电商视频号夏令营(第四期)零基础带你玩转微信视频号

教学内容&#xff1a; 下 载 地 址&#xff1a; laoa1.cn/1821.html 1.剪辑软件整套实例教程0基本一小时懂得视频编辑 1.上课前必看 1.如何获实拍视频的原创素材 2.怎样运送视频水印&#xff0c;提取图片文案脚本 2.如何发布爆款短视频 2.微信视频号基本功能解读 2.直播的时…

软件物料清单(SBOM)生成指南 .pdf

如今软件安全攻击技术手段不断升级&#xff0c;攻击数量显著增长。尤其是针对软件供应链的安全攻击&#xff0c;具有高隐秘性、追溯难的特点&#xff0c;对企业软件安全威胁极大。 同时&#xff0c;软件本身也在不断地更新迭代&#xff0c;软件内部成分安全性在持续变化浮动。…

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

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

WiTUnet:一种集成CNN和Transformer的u型架构,用于改进特征对齐和局部信息融合

WiTUnet:一种集成CNN和Transformer的u型架构&#xff0c;用于改进特征对齐和局部信息融合 摘要IntroductionRelated workMethod WiTUnet: A U-Shaped Architecture Integrating CNN and Transformer for Improved Feature Alignment and Local Information Fusion. 摘要 低剂量…

天锐绿盾 | 如何防止开发部门源代码泄露、外泄?

天锐绿盾是一款专为企业设计的数据防泄密解决方案&#xff0c;尤其针对软件开发部门的源代码保护提供了多维度、全方位的防护措施。 PC访问咨询地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是如何利用天锐绿盾防止公司…

C++ 之 string类的模拟实现

这学习我有三不学 昨天不学&#xff0c;因为昨天是个过去 明天不学&#xff0c;因为明天还是个未知数 今天不学&#xff0c;因为我们要活在当下&#xff0c;我就是玩嘿嘿~ –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–…

【Web】第三次

【Web】第三次 1.完成学校官方网站页面制作2.使用动画完成过渡变换效果 1.完成学校官方网站页面制作 2.使用动画完成过渡变换效果 1.完成学校官方网站页面制作 html&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://…

Kafka 3.x.x 入门到精通(03)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通&#xff08;03&#xff09;——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.2 集群启动2.3 创建主题2.4 生产消息2.4.1 生产消息的基本步骤2.4.2 生产消息的基本代码2.4.3 发送消息2.4.3.1 拦截器2.4.3.1.1 增加拦截器类2.4.3.1.2 配置拦截器 2.4.3…