【JavaEE初阶】第一节.多线程(进阶篇 ) 常见的锁策略、CAS及它的ABA问题
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、常见的锁策略
- 1.1 乐观锁 vs 悲观锁
- 1.2 普通的互斥锁 vs 读写锁
- 1.3 重量级锁 vs 轻量级锁
- 1.4 自旋锁 vs 挂起等待锁
- 1.5 公平锁 vs 非公平锁
- 1.6 可重入锁 vs 不可重入锁
- 二、CAS
- 2.1 CAS典型应用场景
- 2.1.1 使用CAS实现原子类
- 2.1.2 使用CAS实现自旋锁
- 2.2 CAS中的ABA问题(小概率bug)
- 2.2.1 什么是ABA问题
- 2.2.2 ABA问题引发的bug
- 2.2.3 解决ABA问题的办法
- 总结
前言
前几节内容我们介绍了有关多线程基础的有关内容,今天开始我们将进入到多线程的进阶的学习当中;接下来的内容会有很大的难度;希望各位能够认真学习,争取能够很好的掌握今天将要学习的内容;
进阶篇中的很多知识,不再是工作中常用的,但是却是在面试中常考的(俗称:面试造核弹,工作拧螺丝)
提示:以下是本篇文章正文内容,下面案例可供参考
一、常见的锁策略
简单通俗的来说,锁策略就是 加锁的时候是咋加的
1.1 乐观锁 vs 悲观锁
乐观锁:预测接下来锁冲突的概率不大,就会少做一点工作,成本更小;
悲观锁:预测接下来锁冲突的概率很大,就会多做一点工作,成本更大;
举例说明:
比如说,就前段时间,西安那边又有确诊的了;
有居民就比较紧张,就在想是不是要在家里屯点菜啥的(疫情会引起封城,封城会影响买菜),提前屯点菜以备不时之需
这个就可以看作是 悲观锁,花费所需成本较大(买菜、运菜、放在地上......);
当时有居民却认为,由于之前已经有过几次确诊的经历,所以说 已经有了不少经验了,所以封城的概率比较小,不需要提前屯菜(屯了吃不完大概率会坏)
这个就可以看作是 乐观锁,话费所需成本更小;
synchronized
就既是一个悲观锁,也是一个乐观锁,准确的来说 它是一个自适应锁;
如果当前锁冲突概率不大,就以乐观锁的方式运行,往往是纯用户态执行的;
一旦发现锁冲突概率大了,就以悲观锁的方式运行,往往要进入内核,对当前线程进行挂起等待;
1.2 普通的互斥锁 vs 读写锁
synchronized 就属于普通的互斥锁,两个加锁操作之间会发生竞争;
读写锁,把加锁操作细化了 "加读锁" "加写锁";
情况一:
线程A 尝试加写锁,线程B 尝试加写锁,此时 A和B 产生竞争,和普通的锁没有区别;
情况二:
线程A 尝试加读锁,线程B 尝试加读锁,此时 A和B 不产生竞争,和没有加锁一样(多线程读,不涉及修改,是线程安全的)
这种情况是相当普遍的;
情况三:
线程A 尝试加读锁,线程B 尝试加写锁,此时 A和B 不产生竞争(一个读一个写所以不存在竞争),和普通的锁没有区别;
1.3 重量级锁 vs 轻量级锁
重量级锁:锁开销比较大,做的工作比较多;
轻量级锁:锁开销比较小,做的工作比较小;
重量级锁、轻量级锁 与之前所介绍的 乐观锁、悲观锁 差不多(内容上不是完全的区分开),但是最终的着力点还是不一样的
其中,在大部分情况下(不绝对),悲观锁 经常会是重量级锁,乐观锁 经常会是轻量级锁
重量级锁 主要依赖了操作系统提供的锁,使用这种锁,容易产生阻塞等待;
轻量级锁 主要尽量的避免使用操作系统提供的锁,尽量在用户态完成功能,尽量避免用户态和内核态的切换,尽量避免挂起等待;
同时,synchronized 是一个自适应锁,既是轻量级锁,也是重量级锁;
锁冲突不高:轻量级;
锁冲突很高:重量级;
1.4 自旋锁 vs 挂起等待锁
自旋锁 是轻量级锁的具体实现,挂起等待锁 是重量级锁的具体实现;
自旋锁:当发生锁冲突的时候,不会挂起等待,会迅速来尝试看这个锁能不能获取到(更轻量,乐观锁)
特点:
- 一旦锁被释放,就可以第一时间获取到;
- 如果锁一直不释放,就会消耗大量的;
可以看作是一个 不断的循环,可以用一个伪代码来表示:
//自旋锁伪代码,不停的循环 while(抢锁(lock) == 失败) {}
挂起等待锁:发现锁冲突,就挂起等待(更重量,悲观锁)
特点:
- 一旦锁被释放,不能第一时间获取到;
- 在锁被其他线程占用的时候,会放弃CPU资源;
总结:
synchronized 作为轻量级锁的时候,内部是 自旋锁;作为重量级锁的时候,内部是 挂起等待锁;
1.5 公平锁 vs 非公平锁
啥样的情况才算是公平?
一般认为,符合 "先来后到" 这样的规则,就是公平!!!
公平锁:多个线程等待一把锁的时候,谁先来尝试拿着一把锁,这把锁就是谁的;
非公平锁:多个线程等待一把锁的时候,就和哪个线程先来后到没有关系,每个线程拿到锁的概率是均等的;
synchronized 是非公平锁;
1.6 可重入锁 vs 不可重入锁
一个线程连续加锁两次,不会造成死锁,那么这个锁就叫做 可重入锁;
一个线程连续加锁两次,会造成死锁,那么这个锁就叫做 不可重入锁;
代码示例:
private static void func() {//......进行一些多线程操作//第一次加锁synchronized (Demo27.class) {//第二次加锁synchronized (Demo27.class) {}}}
如上述代码,第一次加锁能够成功,Demo27.class 处于被加锁的状态;但是 第二次加锁,由于 Demo27.class 已经是被加锁的状态了,所以就会呈现出 阻塞状态;
要等待第一次加锁释放掉,第二次加锁才能够成功;但是 要想第一次加锁释放,那么 又必须要到第二次加锁成功之后,代码往下执行 .
这样就构成了一个死循环,就叫做 死锁!!!
synchronized 属于可重入锁;
二、CAS
CAS 是操作系统硬件 给JVM提供的另外一种更轻量的原子操作的机制;
准确来说,CAS是CPU提供的一条特殊的指令 —— compare and swap(比较和交换);
CAS 是一个原子指令;
比较:是比较内存和寄存器的值;
如果相等,则把寄存器和另一个值进行交换;如果不相等,就不进行操作;
代码实现:
//CAS 的伪代码来理解它的工作流程 //其中,address表示内存地址,expextValue表示一个寄存器中 用来比较的值, //expextValue表示另一个寄存器中 用来交换的值 boolean CAS(address,expextValue,swapValue) {if(&address == expextValue) {&address = swapValue;return true;}return false; } //上面一系列操作都是由一个CPU指令来完成的
2.1 CAS典型应用场景
2.1.1 使用CAS实现原子类
原子类:这是标准库中提供的一组类,可以让原子的进行 ++、-- 等运算 ;
代码实现:
package thread;public class Demo28 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count);} }
在之前,我们已经介绍过,最终的结果不是 10_0000 ;
运行结果:
我们可以使用加锁来解决这个问题,也可以使用原子类来解决这个问题:
package thread;import java.util.concurrent.atomic.AtomicInteger;public class Demo28 {//public static int count = 0;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++;//这个方法相当于count++count.getAndIncrement();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {//count++;count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count);} } //和之前的不同的代码已注释,这是使用 原子类来解决问题的,没有使用加锁操作,也实现了线程安全
运行结果:
在Java标准库 里面提供了基于CAS所实现的 "原子类",是线程安全的;
这些 "原子类" 通常以 Atomic 开头,对常用的 int、long等等 进行了封装,如:
2.1.2 使用CAS实现自旋锁
代码实现:
//自旋锁伪代码 public class SpinLock {private Thread owner = null;public void lock() {//当前的owner是否为空,为空即为当前没有加锁,于是就进行交换,//把当前要给加锁的线程的值赋予owner//非空就不去进行交换,就循环继续进行,呈现自旋的状态while(!CAS(this.owner,null,Thread.currentThread())) {}} }
当 owner 为 null 的时候 CAS 才能成功,循环才能结束;
当 owner 为非null,这说明当前的锁已经被其他线程给占用了,因此 就需要继续循环(自旋);
2.2 CAS中的ABA问题(小概率bug)
2.2.1 什么是ABA问题
ABA问题可以单纯的这样理解:如果你去买一个手机,那么你无法区分 它是一个新机,还是一个翻新机(二手的、外面包装和新机一样);
类似的,在CAS里面,也无法区分,数据始终就是A;还是数据从 A 变成 B,之后又变回了 A ;
如果是前者,那么一点问题都没有;但是如果是后者,那么 CAS 就会有一定的概率引发 bug(极端情况下的小概率事件)
图示示例:
2.2.2 ABA问题引发的bug
这里结合一个具体的例子,来介绍ABA问题引发的bug;
举例说明;
假设滑稽老铁有 1000 存款,此时想要从 ATM机 上取走 500(ATM机 按照CAS的方式来进行操作)
取钱的时候,按下取款按钮,就会触发一个 "取钱的线程",但是 滑稽老铁手一滑,连续按了两下(即 产生了两个线程)
但是,怕就怕在这期间 突然又来了一个线程(比如说 滑稽老铁的一个朋友,此时正好向滑稽老铁转了500)
这时候,就扣除了两次钱了,这个就是典型的ABA问题(极端情况下的小概率问题);
此时,线程2不知道 当前的1000,始终是1000;还是 1000 -> 500 -> 1000 ;
2.2.3 解决ABA问题的办法
正经的解决ABA问题的办法,是想办法获取到中间过程 —— 引入一个 "版本号" 来解决 ;
在上述的例子当中,CAS是比较的是 余额,余额相同,就可以进行修改(余额是可以变大和变小,所以就会出现ABA问题)
但是,如果换成 "版本号",并且规定 "版本号" 只能增不能减,那么就不会出现ABA问题;
当然,解决ABA问题的办法肯定不止这一种,这里只是列举了一种非常典型的办法 ;
总结
好了,这篇博客到这里就已经结束了
本篇博客主要介绍的是 各种常见的锁策略,以及CAS、CAS中的小概率bug —— ABA问题,并且介绍了ABA问题的解决方案 ;
相关文章:

【JavaEE初阶】第一节.多线程(进阶篇 ) 常见的锁策略、CAS及它的ABA问题
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、常见的锁策略 1.1 乐观锁 vs 悲观锁 1.2 普通的互斥锁 vs 读写锁 1.3 重量级锁 vs 轻量级锁 1.4 自旋锁 vs 挂起等待锁 1.5 公平…...

Linux基础命令-pstree树状显示进程信息
Linux基础命令-uname显示系统内核信息 Linux基础命令-lsof查看进程打开的文件 Linux基础命令-uptime查看系统负载 文章目录 前言 一 命令介绍 二 语法及参数 2.1 使用man查看命令语法 2.2 常用参数 三 参考实例 3.1 以树状图的形式显示所有进程 3.2 以树状图显示进程号…...

keepalived+LVS配置详解
keepalivedLVS配置详解keepalived简介keepalived的应用场景keepalived工作原理VRRP协议核心组件分层工作工作状态LVS简介LVS三种模式NAT模式(网络地址映射)IPTUN模式(IP隧道)DR模式(直接路由)三种模式对比keepalivedLVS配置1.master配置2. keepalived配置文件3 修改keepalived配…...

Unity之C#端使用protobuf
什么是protobuf protobuf全称Protocol Buffers,由Google推出的一种平台、语言无关的数据交互格式,目前使用最广泛的一种数据格式,尤其在网络传输过程中,有很强的安全性,而且数据量比json和xml要小很多。 最主要的是pr…...

C++设计模式(18)——模板方法模式
亦称: Template Method 意图 模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 问题 假如你正在开发一款分析公司文档的数据挖掘程序。 用户需要向程序输入各种格式…...

SQLserver 索引碎片
Oracle 不需要整理碎片,原因? 1. rowid 默认的索引是B-树索引。索引建立在表中的一个或多个列或者是表的表达式上,将列值和行编号一起存储。行编号是唯一标记表中行的伪列。 行编号是物理表中的行数据的内部地址&am…...

【Storm】【二】安装
1 准备 1.1 准备linux服务器 本文搭建的是3节点的集群,需要3台linux服务器,我这里使用的是centos7版本的linux虚拟机,虚拟机网络配置如下: 主节点: master 192.168.92.90 从节点: slave1 192.168.92.…...

Android ConditionVariable
Android ConditionVariable 线程操作经常用到wait和notify,用起来稍显繁琐,而Android给我们封装好了一个ConditionVariable类,用于线程同步。提供了三个方法block()、open()、close()。 void block() //阻塞当前线程,直到条件为…...

Action Segmentation数据集介绍——Breakfast
文章目录简介细节Cooking actibitiesillustration of the actions论文讲解Breakfast(The Breakfast Action Dataset)简介 早餐动作数据集包括与早餐准备相关的10个动作,由18个不同厨房的52个不同的人执行。该数据集是最大的完全带注释的数据…...

横道图时间标尺在P6软件中的设置
卷首语 由于其直观简洁且易于管理的特性,使其成为展示项目活动顺序及时间安排的最常用的进度管理工具。 甘特图 甘特图(Gantt Chart),又称为横道图或棒条图,是最早的项目进度管理工具之一。由于其直观简洁且易于管理…...

空间复杂度(超详解+例题)
全文目录引言空间复杂度例题test1test2(冒泡排序)test3(求阶乘)test4(斐波那契数列)总结引言 在上一篇文章中,我们提到判断一个算法的好坏的标准是时间复杂度与空间复杂度。 时间复杂度的作用…...

Document-Level event Extraction via human-like reading process 论文解读
Document-Level event Extraction via human-like reading process 论文:2202.03092v1.pdf (arxiv.org) 代码:无 期刊/会议:ICASSP 2022 摘要 文档级事件抽取(DEE)特别困难,因为它提出了两个挑战:论元分散和多事件。第一个挑战…...

H5盲盒抽奖系统源码
盲盒抽奖系统4.0,带推广二维码防洪炮灰功能和教程。 支持微信无限回调登录 标价就是源码价格,vuetp5框架编写,H5网页,前后端分离 此源码为正规开发,正版产品已申请软著。 开源无加密无授权,可以二开使用…...

低代码平台和无代码平台哪个更适合开发企业管理系统?
编者按:本文分析了开发企业管理系统所需要的平台特性,并根据这些特点和低代码无代码的优劣比较,得出低代码平台更适合开发企业管理系统。关键词:私有化部署,可视化设计,源码交付,数据集成&#…...

75岁彪马再发NFT 复活美洲狮IP
在“运动品牌Web3”的潮流里,彪马(PUMA)绝对算是发烧友级别。2月22日,这家德国服装品牌的新NFT又来了,总量10000个Super PUMA NFT中,将有4000个以0.15 ETH(约为255美元)价格正式公售…...

大学生成人插画培训机构盘点
成人插画培训机构哪个好,成人学插画如何选培训班?给大家梳理了国内较好的插画培训机构排名,各有优势和特色,供大家参考! 一:国内成人插画培训机构排名 1、轻微课(五颗星) 主打课程有…...

【算法基础】一维差分 + 二维差分
👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和算法 ✈️专栏:【C/C】算法 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有…...

游戏服务器框架 技能buff篇
游戏服务器框架 技能buff篇 1.状态 state 全局API 用于定义各种状态检查 bool IsDead(){ // 死亡buff if (buff->id 10001){ return true; } return false; } bool IsInvincible(){ if (buff->id 20001 || buff->id 20002){…...

网友说socket通信讲的不彻底,原来这才是Socket
关于对 Socket 的认识,大致分为下面几个主题,Socket 是什么,Socket 是如何创建的,Socket 是如何连接并收发数据的,Socket 套接字的删除等。 Socket 是什么以及创建过程 一个数据包经由应用程序产生,进入到…...

Nginx第二讲
目录 二、Nginx02 2.1 keepalived和heartbeat介绍 2.1.1 两者的介绍 2.1.2 keepalived简介 2.1.3 VRRP协议与工作原理 2.1.4 Keepalvied的工作原理 2.2 安装环境及keepalived 2.3 启动与验证keepalived 2.4 keepalived测试 2.4.1 环境准备 2.4.2 配置keepalived 2.…...
redis(win版)
1. 前言1.1 什么是RedisRedis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。基于内存存储,读写性能高适合存储热点数据&am…...

【Linux】编辑器——vim(最小集+指令集+自动化配置)
目录 1.vim最小集 1.1 vim的三种模式 1.2 vim的基本操作 2.vim指令集 2.1 命令模式指令集 移动光标 删除文字 复制 替换 撤销上一次操作 更改 跳至指定的行 2.2 底行模式指令集 列出行号 跳到文件中的某一行 查找字符 保存文件 多文件操作 3.如何配置vim 配…...

Centos7+Xshell+Jenkins堆装
windows系统崩坏,重装测试类工具,心情崩了 windows硬盘损坏前,运行应用具慢。。。。。。慢着慢着就走了 从前部署在本地的jenkins,python,gitblit等相关脚本都凉透了,所以这次把服务部署到Centos7上…...

Android system实战 — Android R(11) 进程保活白名单
Android system实战 — Android R 进程保活白名单0. 前言1. 具体实现1.1 准备工作1.2 源码实现1.2.1 源码1.2.2 diff文件0. 前言 最近在Android R上实现一些需求,进行记录一下,关于进程保活的基础知识可以参考Android system — 进程生命周期与ADJ&#…...

oracle表 分组,并查每组第一条
oracle主要用到的函数:OVER(PARTITION BY) mysql主要用到的函数:LIMIT (用到3个地方:分组列、组内排序列、表名) oracle: select t.* from ( select a.*, ROW_NUMBER() OVER (PARTITION BY 分组列 ORDER BY 组内排…...

Java代码弱点与修复之——DE: Dropped or ignored exception(无视或忽略异常)
弱点描述 Dropped or ignored exception(DE)指的是在代码中抛出的异常被捕获后被无视或忽略了,而不是被适当地处理。这种情况通常发生在程序员没有处理异常或处理异常时不小心忽略了异常的情况下。 Dropped or ignored exception会导致程序无法正常工作,因为异常会阻塞程…...

JavaEE简单示例——动态SQL之更新操作<set>元素
简单介绍: 在之前我们做的学生管理系统的时候,曾经有一个环节是修改学生的数据。我们在修改的时候是必须将student对象的三个属性全部填入信息,然后全部修改才可以,这样会造成一个问题就是在我们明明只需要修改一个属性的时候却要…...

【极海APM32替代笔记】低功耗模式配置及配置汇总
【极海APM32替代笔记】低功耗模式配置及配置汇总 文章总结:(后续更新以相关文章为准) 【STM32笔记】低功耗模式、WFI命令等进入不了休眠的可能原因(系统定时器SysTick一直产生中断) 【STM32笔记】HAL库低功耗模式配置…...

攻击者失手,自己杀死了僵尸网络 KmsdBot
此前,Akamai 的安全研究员披露了 KmsdBot 僵尸网络,该僵尸网络主要通过 SSH 爆破与弱口令进行传播。在对该僵尸网络的持续跟踪中,研究人员发现了一些有趣的事情。 C&C 控制 对恶意活动来说,最致命的就是夺取对 C&C 服务…...

东阿县高新技术企业认定条件和优惠政策 山东同邦科技分享
东阿县高新技术企业认定条件和优惠政策 山东同邦科技分享 高新技术企业 在《国家重点支持的高新技术领域》内,持续进行研究开发与技术成果转化,形成企业核心自主知识产权,并以此为基础开展经营活动,在中国境内(不包…...