多线程进阶:常见的锁策略、CAS

之前我们所了解的属于多线程的初阶内容。今天开始,我们进入多线程进阶的学习。

锁的策略

乐观锁 悲观锁

这不是两把具体的锁,应该叫做“两类锁”

乐观锁:预测锁竞争不是很激烈(这里做的工作可能就会少一些)

悲观锁,预测锁竞争会很激烈(这里的工作可能就会多一些)

这里都不绝对,悲观和乐观,唯一的区分主要就是看预测锁竞争激烈程度的结论~

轻量级锁 重量级锁

轻量级锁加锁解锁开销比较小~效率更高~

重量级锁加锁解锁开销比较大~效率更低~

多数情况下,乐观锁也是一个轻量级锁

多数情况下,悲观锁也是一个重量级锁

自旋锁 挂起等待锁

自旋锁,是一种典型的轻量级锁:当某个线程没有申请到锁的时候,该线程不会被挂起,而是每隔一段时间检测锁是否被释放。如果锁被释放了,那就竞争锁;如果没有释放,过一会儿再来检测。

挂起等待锁,是一种典型的重量级锁:当某个线程没有申请到锁的时候,此时该线程会被挂起,即加入到等待队列等待。当锁被释放的时候,就会被唤醒,重新竞争锁。

也就是说自旋锁在申请锁的过程中会一直重复申请,会占用大量的cpu资源。挂起等待锁也就可以把cpu省下来干别的事情~

互斥锁 读写锁

互斥锁:像synchronized这样的锁,提供加锁和解锁两个操作~

如果一个线程加锁了,另一个线程也尝试加锁就会阻塞等待~

读写锁:只有一组操作中有读也有写,才会产生竞争。

公平锁 非公平锁

此处应该把公平定义成“先来后到”。

操作系统和Java synchronized 原生都是“非公平锁”。操作系统这里的针对加锁的控制,本身就非常依赖线程调度顺序,这个调度顺序是随机的,不会考虑到这个线程等待多久了~

要想实现公平锁,就得在这个基础上,引入额外的东西(比如一个给锁排序的队列)

可重入锁 不可重入锁

不可重入锁:针对同一个线程连续加锁多次,会出现死锁

可重入锁:针对同一个线程连续加锁多滴,不会出现死锁

synchronized

1.synchronized既是一个悲观锁,又是一个乐观锁。

        synchronized默认是一个乐观锁,但是如果发现当前锁竞争比较激烈,就会变成悲观锁。

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

        synchronized默认是一个轻量级锁,如果发现当前锁竞争比较激烈,就会转换成重量级锁。

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

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

4.synchronized不是读写锁

5.synchronized是非公平锁

6.synchronized是可重入锁

总结:上述谈到的6种锁策略,可以视为是“锁的形容词”。

CAS

CAS全称Compare and swap,字面意思“比较和交换”

一个 CAS 涉及到以下操作:

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

1. 比较 A 与 V 是否相等。(比较)

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

3. 返回操作是否成功。

最重要的是:CAS的操作是原子的!

CAS的过程并非是通过一段代码实现的,而是通过一条CPU指令完成的。

既然是原子的,那么就可以一定程度上处理线程安全问题~

boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

通过一段伪代码(不能编译运行,只是表达了一个大概的逻辑)可以更好地理解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;}
}此处为伪代码

可以把oldvalue理解成为寄存器里的值。

我们就拿伪代码来说明:

正常情况下,oldValue应该和value是一样的值,紧接着这里会产生CAS,把oldValue + 1写到value中去。

但是:可能会执行完把value的值写到oldvalue(寄存器)这一步后,线程发生切换了,另一个线程也进行修改了value的值

此时这个线程回来后,通过CAS判定,就认为value和oldvalue不相等了。

于是就返回false,不进行任何交换。进入循环,循环内部重新读取value的值到oldvalue中去。

此时在比较,发现相等了,进行CAS操作,并返回true,就不进入循环结束了。

原子类这里的实现,每次修改之前,再确认一下这个值是否符合要求。

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

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

while循环中:监测当前的owner是否是null,如果是null,就进行交换,也就是把当前线程的引用赋值给owner 。如果赋值成功,此时循环结束,加锁完成了。
如果当前锁,已经被别的线程占用了,CAS就会发现,,this.owner 不是null,CAS就不会产生赋值,也同时返回false.循环继续执行,并进行下次判定....
 

ABA问题

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

但是这里的一致,可能是没改过,也可能是改过,但是改回来了。

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

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相同,再次执行扣款操作

这个时候扣款就被执行了两次。

解决办法

给要修改的值,引入版本号。在 CAS 比较数据当前值和旧值的同时,也要比较版本号是否符合预期。CAS 操作在读取旧值的同时,也要读取版本号。真正修改的时候,如果当前版本号和读到的版本号相同,则修改数据,并把版本号 + 1 。如果当前版本号高于读到的版本号,操作失败(认为数据已经被修改过了)。

synchronized原理

两个线程,针对一个对象加锁,就会产生阻塞等待。

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

1.锁升级、锁膨胀

1)无锁  2)偏向锁  3)轻量级锁  4)重量级锁

synchronized(locker){
}

当代码执行到这个代码块中之后,加锁过程会经历前面说的这几个阶段:

偏向锁:

进行加锁的时候,首先会进入到偏向锁的状态。偏向锁的过程就是:“非必要,不加锁”。

synchronized的时候,并不是真的加锁,先偏向锁状态,做个标记(这个过程是非常轻量的),如果整个使用锁的过程中,都没有出现锁竞争,在synchronized执行完之后,取消偏向锁即可。

(反正也没有锁竞争,这样就开销最低了~)

但是如果使用过程中,另一个线程也尝试加锁,在它加锁之前,迅速地把偏向锁升级成真正的加锁状态,另一个线程也就只能阻塞等待了~

轻量级锁:

当synchronized发生锁竞争的时候,就会从偏向锁,升级成轻量级锁。

此时,synchronized相当于是通过自旋的方式来进行加锁的。刚才那个CAS那里的那个伪代码一样~

synchronized内部的自旋循环中,有个计数器,记录了循环了多少次/多久了,达到一定程度,就结束循环,执行重量级锁的逻辑。

重量级锁:

此时如果线程进行了重量级锁的加锁,并且发生锁竞争,此时线程就会被放到阻塞队列中,暂时不参与CPU调度了~

然后锁被释放了这个线程才有机会被调度到,并且有机会获取到锁。

锁降级:

锁能升级了,但是不能降级。只有锁升级,没有锁降级。除非是另外搞一个对象,重复刚才的从偏向锁开始升级的过程~

2.锁消除

编译器智能的判定,看当前的代码是否是真的要加锁,如果这个场景不需要加锁,程序猿也加了,就自动把锁给干掉~~
StringBuffer关键方法都带有synchronized。
但是如果在单线程中使用StringBuffer, synchronized 加了也白加,此时编译器就会直接把这些加锁操作干掉了。

3.锁粗化

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

通常情况下,认为锁的粒度细一点比较好。加锁的部分,是不能并发执行的。锁的粒度越细,能并发的代码就越多,反之就越少。

但是有些情况下,锁的粒度粗一些反而更好~


 

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

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

相关文章

3.6+铁死亡+WGCNA+机器学习

今天给同学们分享一篇3.6铁死亡WGCNA机器学习的生信文章“Identification of ferroptosis related biomarkers and immune infiltration in Parkinsons disease by integrated bioinformatic analysis”,这篇文章于2023年3月14日发表在BMC Med Genomics期刊上&#…

Mac电脑信息大纲记录软件 OmniOutliner 5 Pro for Mac中文

OmniOutliner 5 Pro是一款专业级的Mac大纲制作工具,它可以帮助用户更好地组织和管理信息,以及制作精美的大纲。以下是OmniOutliner 5 Pro的主要功能和特点: 强大的大纲组织和管理功能。OmniOutliner 5 Pro为用户提供了多层次的大纲结构&…

【QT】QT事件Event大全

很高兴在雪易的CSDN遇见你 ,给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 本文分享QT中的事件Event技术,主要从QT事件流程和常用QT事件方法等方面展开,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞关注,小易…

virtualbox安装linux虚拟机访问互联网(外网)的方法

virtualbox安装linux虚拟机访问互联网(外网)的方法 设置方法效果图 设置方法 效果图

Linux系统编程-文件

目录 1、系统编程介绍以及文件基本操作文件编程系统调用文件基本读写练习 2、文件描述符以及大文件拷贝文件描述符大文件拷贝对比试验 3、文件实战练习 1、系统编程介绍以及文件基本操作 系统编程是基于Linux封装好的一些函数,进行开发。 Linux文件信息属性在indo…

用AI解决量子学问题

3 人工智能用于量子力学 在这一部分中,我们提供了有关如何设计高级深度学习方法以有效学习神经波函数的技术评述。在第3.1节中,我们概述了一般情况下定义和解决量子多体问题的方法。在第3.2节中,我们介绍了学习量子自旋系统基态的方法。在第…

微信收款码费率0.38太坑了

作为一个有多年运营经验的商家,我本人在申请收款功能时曾经走过了不少弯路。我找遍了市面上的知名的支付公司,但了解到的收款手续费率通常都在0.6左右,最低也只能降到0.38。这个过程吃过不少苦头。毕竟,收款功能是我们商家的命脉&…

Java笔记三

包机制: 为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。 包语句的语法格式为:pack pkg1[. pkg2[. pkg3...]]; 般利用公司域名倒置作为包名;如com.baidu.com,如图 导包: 为了能够…

Python学习 day01(注意事项)

注释 print语句 变量 数据类型的转换 运算符 / 的结果为浮点数。若// 的两边有一个为浮点数,则结果为浮点数,否则为整数。 字符串 7. 精度控制 8. input()

五、点击切换、滚动切换、键盘切换

简介 通过事件改变当前展示的信息组件,交互的事件有点击上下切换、鼠标轮动上下切换、键盘上下键切换。欢迎访问个人的简历网站预览效果 本章涉及修改与新增的文件:App.vue、public 一、鼠标点击上下箭头切换 <template><div class="app-background"…

python学习--类的浅拷贝与深拷贝

变量的赋值操作 只是形成两个变量&#xff0c;实际上还是指向同一个对象 浅拷贝 Python拷贝一般都是浅拷贝&#xff0c;拷贝时&#xff0c;对象包含的子对象内容不拷贝&#xff0c;因此&#xff0c;源对象与拷贝对象会引用同一个子对象 深拷贝 使用copy模块的deepcopy函数…

古彝文识别:文化遗产的数字化之旅

目录 &#x1f345;前言&#x1f353;古彝文介绍&#x1f353;古彝文识别的重难点&#x1f352;原籍难以获取&#xff0c;传统翻译过程繁琐&#xff0c;周期长。&#x1f352;版式多样&#xff0c;笔画相近。&#x1f352;图像质量差&#xff0c;手写识别难。&#x1f352;古彜…

成为威胁:网络安全中的动手威胁模拟案例

不断变化的网络威胁形势要求组织为其网络安全团队配备必要的技能来检测、响应和防御恶意攻击。然而&#xff0c;在研究中发现并继续探索的最令人惊讶的事情是&#xff0c;欺骗当前的网络安全防御是多么容易。 防病毒程序建立在庞大的签名数据库之上&#xff0c;只需更改程序内…

Vue组件库Element

目录 Vue组件库ElementElement简介Element快速入门环境配置Element常用组件Table表格Table表格演示Table表格属性详解 Pagination分页Pagination分页演示Pagination分页属性详解Pagination分页事件详解 Dialog对话框Dialog对话框组件演示Dialog对话框属性详解 Form表单Form表单…

使用nvm快速切换node版本,windows安装nvm实现管理node版本

使用nvm node管理工具管理node版本&#xff0c;可以做到node版本随意切换&#xff0c;能快速的降低、升高node版本 这里写目录标题 使用nvm node管理工具管理node版本&#xff0c;可以做到node版本随意切换&#xff0c;能快速的降低、升高node版本安装和使用步骤&#xff1a; 一…

XC6206 低压线性稳压器 300mA低功耗LDO

XC6206系列是一款采用CMOS和激光修整技术制造的高精度、低能耗、3端子、正电压调压器。该系列提供了一个大的电流和一个明显的小的辍学电压。 XC6206由限流器电路、驱动器晶体管、精确参考电压和纠错电路组成。该系列兼容低ESR陶瓷电容。电流限制器的折叠电路作为短路保护以及输…

如何通过Gunicorn和Niginx部署Django

本文主要介绍如何配置Niginx加载Django的静态资源文件&#xff0c;也就是Static 1、首先需要将Django项目中的Settings.py 文件中的两个参数做以下设置&#xff1a; STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, static) 然后在宝塔面板中执行python manage.…

86、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->String相关命令

本次讲解要点&#xff1a; String相关命令&#xff1a;String是指value中的数据类型 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.…

10.12广州见 | 第十六届智慧城市大会报名通道全面开启

第十六届中国智慧城市大会 将于10月12日至13日 在广州举办 智慧城市是数字中国、智慧社会的核心载体&#xff0c;是数字时代城市发展的高级形态。由中国服务贸易协会、中国测绘学会、中国遥感委员会主办的第十六届中国智慧城市大会&#xff0c;将以“数实融合开放创新智引未…

【Java基础-JDK21新特性】它发任它发,我用java8

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…