常见锁策略之可重入锁VS不可重入锁

可重入锁VS不可重入锁

有一个线程,针对同一把锁,连续加锁两次,如果产生了死锁,那就是不可重入锁,如果没有产生死锁,那就是可重入锁.

死锁

我们之前引入多线程的时候不是讲了一个加数字的案例么,我们今天以它来举例

当我们这样写的时候会出现什么问题?

分析:第一个synchronized的加锁对象是 this ,当我们继续执行代码,就会发现第二个synchronized的加锁对象还是 this ,此时就需要注意,当一个对象已经被加锁了,此时尝试对这个已经加锁的对象再一次的进行加锁,就会出现"锁竞争",我们要想使得第二个synchronized实现对 this 的加锁,就要让increase执行完毕,但是要想让increase执行完毕,就需要第二个synchronized加锁成功,此时就陷入了循环,就出现了矛盾.此时这个代码就卡在这里了,因此这个线程就僵住了.这是死锁的第一个体现形式.

这里的关键在于,两次加锁,都是"同一个线程",第二次尝试加锁的时候,该线程已经有了这个锁的权限了,这个时候,不应该加锁失败的,不应该阻塞等待的.

如果是一个不可重入锁,这把锁不会保存,是哪个线程对它加的锁,只要它当前处于加锁状态之后,收到了"加锁"这样的请求,就会拒绝当前加锁,而不管当下的线程是哪个,就会产生死锁.

如果是一个可重入锁,则是会让这个锁保存,是哪个线程加上的锁,后续收到加锁请求之后,就会先对比一下,看看加锁的线程是不是当前自己持有这把锁的线程,这个时候就可以灵活判定了.

但庆幸的是,synchronized本身就是一个可重入锁,实际上我们上述的那个举例子的代码并不会出现死锁的情况.

首先,答案是肯定的,不能释放,如果在这里释放锁了,那么中间的synchronized以及中间的代码就不会受到锁的保护了.

那么我们想一下,可重入锁,需要比不可重入锁额外多出哪些功能

1.判断当前加锁的线程是不是同一个线程

2.判断当前代码执行到的是第几层的锁,那么这个功能是怎么实现的呢?

其实很简单,持有一个"计数器"就可以了,让锁对象不光要记录是哪个线程持有锁,同时再通过一个整形变量记录当前这个线程加了几次锁,每遇到一个加锁操作,就计数器+1,每遇到一个解锁操作就-1,当计数器减为0的时候,才真正执行释放锁的操作,其他时候不释放锁.而类似于这个操作的操作,我们称它为"引用计数"

死锁的三种典型情况

1.一个线程,一把锁,但是是不可重入锁,该线程针对这个锁连续加锁两次,就会出现死锁

2.两个线程,两把锁,这两个线程先分别获取到一把锁,然后再同时尝试获取对方的锁

下面我们敲代码来理解一下死锁

首先,这是一个错误的代码

执行结果如下

实际上由刚才的分析可以知道,这里会出现死锁的情况,那么为什么这里和理论值不一样呢?

因为没有加Sleep

代码如上

执行结果如下

那么,为什么?为什么加了Sleep之后会不一样,究竟是Sleep改变了线程原有的样子,还是说Sleep恢复了线程原有的样子?

答案是后者

我们在使用多线程的时候会发现,程序运行的时间特别长了会经常出现一些问题,或者说当我们来气了多个线程他们分别执行几个任务,但是因为执行的任务的时间非常短,有时候CPU切换的时候会出现一系列的问题.

原因是当我们设置Sleep是,就等于告诉CPU,当前的线程不再运行,持有当前对象的锁.那么这个时候CPU就会切换到另外的线程了,这种操作在有些时候是非常好的.

3.N个线程M把锁

哲学家就餐问题

每个哲学家,主要做两件事

1.思考人生,会放下筷子

2.吃面.会拿起左手和右手的筷子

3.每个哲学家,什么时候思考人生,什么时候吃面条,都不好说

2.每个哲学家一旦想吃面条了,就会非常固执的完成吃面条的这个操作,如果此时,他的筷子被别人使用了,就会阻塞等待,而且等待的过程中不会放下手中已经拿着的的筷子

是否有办法去避免死锁呢?

先明确产生死锁的原因,死锁的必要条件

四个必要条件(缺一不可,只要破坏其中任意一个条件,就可以避免死锁)

1.互斥使用,一个线程获取到一把锁之后,别的线程不能获取到这个锁(我们实际使用的锁,一般都是互斥的(锁的基本特性))

2.不可抢占,锁只能是被持有者主动释放,而不是被其他线程直接抢走(锁的基本特性)

3.请求和保持,这一个线程尝试去获取多把锁,在获取第二把锁的过程中,会保持对第一把锁的获取状态(取决于代码结构)(很可能会影响到需求)

在获取第二把锁的同时会保持对第一把锁的状态,这里由于获取第二把锁的时候,并没有去释放第一把锁,所以就会出现阻塞等待

当我们将代码改成这样,即获取完第一把锁之后,并且将第一把锁释放掉,此时再去请求获取第二把锁,这样做是不会出现死锁的

4.循环等待.t1尝试获取 locker2,需要t2执行完,释放locker2;t2尝试获取locker1,需要t1执行完,释放locker1 (取决于代码结构)(解决死锁问题的最关键要点)

如果具体解决死锁问题,实际的方法有很多种(例:银行家算法(但不推荐,因为不接地气))

介绍一个更简单,也非常有效的解决死锁的办法

针对锁进行编号,并且规定加锁的顺序

比如,约定,每个线程如果要获取多把锁,必须先获取编号小的锁,后获取编号大的锁.只要所有线程加锁的顺序,都严格遵守上述顺序,就一定不会出现循环等待.

像这样,我们规定先让locker1加锁,然后让locker2加锁,按照指定顺序加锁,也就可以避免死锁的问题了

synchronized具体是采用了哪些锁策略

1.synchronized即是悲观锁,也是乐观锁.

2.synchronized即是重量级锁,也是轻量级锁.

3.synchronized重量级锁部分是基于系统的互斥锁实现的,轻量级锁部分是基于自旋锁实现的.

4.synchronized是非公平锁(不会遵守先来后到,锁释放了之后,哪个线程拿到锁,各凭本事).

5.synchronized是可重入锁(内部会记录哪个线程拿到了锁,记录引用次数).

6.synchronized不是读写锁.

synchronized内部实现策略(内部原理)

代码中写了一个synchronized之后,这里可能会产生一系列的"自适应的过程",锁升级(锁膨胀)

无锁->偏向锁->轻量级锁->重量级锁

偏向锁(懒汉模式思想的延伸)

不是真的加锁,而只是做了一个"标记".如果有别的线程来竞争锁了,才会真的加锁,如果没有别的线程竞争,就自始至终都不会真的加锁了.(加锁本身,有一定的开销,能不加就不加,非得是有人来竞争了,才会真的加锁)

偏向锁在没有其他人竞争的时候,就仅仅是一个简单的标记(非常轻量).一旦有别的线程尝试加锁,就会立刻把偏向锁升级为一个真正的加锁状态,让其他线程只能阻塞等待

轻量级锁

synchronized通过自旋的方式来实现轻量级锁,我这边把锁占据了,另一个线程就会按照自旋的方式,来反复查询当前的锁的状态是不是被释放了.但是,后续如果竞争这把锁的线程越来越多了(锁冲突更激烈了),就会从轻量级锁,升级成重量级锁

锁消除

编译器,会智能的判定,当前的这个代码,是否有必要加锁,如果你写了加锁,但实际上没有必要加锁,就会把加锁操作自动优化掉。

比如在单个线程中使用StringBuffer.

编译器进行优化,是要保证优化之后的逻辑和之前的逻辑是一致的

锁粗化

关于"锁的粒度"如果加锁操作里包含的实际要执行的代码越多,就认为锁的粒度越大.

//以下是一些伪代码
//锁的粒度小
for(.....){
    synchronized(this){
        count++;    
    }
}
//锁的粒度大
synchronized(this){
    for(.....){
        count++;    
    }
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Zookeeper:Zookeeper集群角色

文章目录 一、Leader选举二、Zookeeper集群角色 一、Leader选举 Serverid:服务器ID;比如有三台服务器,编号越大在选择算法中的权重越大。Zxid:数据ID;服务器中存放的最大数据ID,值越大说明数据越新&#x…

【创作纪念日】我的CSDN1024创作纪念

机缘 注册CSDN是很长时间了,但是上学时因为专业是电气工程,与编程打交道比较少,一直都是寻求帮助,而非内容输出。直到考研后专业改变,成为了主要跟软件编程、计算机知识相关的研究后,才逐步开启自己的CSDN…

模拟布局:为什么井、抽头和保护环至关重要

其中的关键示例是井、抽头和保护环。这些结构对于任何 MOSFET 电路的工作都至关重要。 这就是为什么了解衬底在 MOSFET 电路中的作用对于创建有效的模拟设计至关重要。要做到这一点,首先必须了解 MOSFET 晶体管的工作原理。 让我们来看看一种类型的 MOSFET&#x…

归并排序-MergeSort (C语言详解)

目录 前言归并排序的思想归并排序的递归法归并排序的非递归法归并排序的时间复杂度与适用场景总结 前言 好久不见, 前面我们了解到了快速排序, 那么本篇旨在介绍另外一种排序, 它和快速排序的思想雷同, 但又有区别, 这就是归并排序, 如下图, 我们对比快速排序与归并排序. 本…

编译器的控制流图分析

1&#xff0c;建立感性认识 1.1 源码 hello.c int x 10; int y 11; int main(){int z 12;for (int i 0;i < 10;i){z * x * y;}if(z>7.0)z1.0f;elsez 2.0f;return 0; }1.2 编译 2005 sudo apt-get install -y graphviz-doc libgraphviz-dev graphviz2034 ../ex_…

Java学习高级一

修饰符 static 类变量的应用场景 成员方法的分类 成员变量的执行原理 成员方法的执行原理 Java之 main 方法 类方法的常见应用场景 代码块 设计模式 单例设计模式 饿汉式单例设计模式 懒汉式单例设计模式 继承 权限修饰符

LeetCode题练习与总结:二叉树的后序遍历--145

一、题目描述 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a…

以太坊DApp交易量激增83%的背后原因解析

引言 最近&#xff0c;以太坊网络上的去中心化应用程序&#xff08;DApp&#xff09;交易量激增83%&#xff0c;引发了广泛关注和讨论。尽管交易费用高达2.4美元&#xff0c;但以太坊仍在DApp交易量方面遥遥领先于其他区块链网络。本文将深入探讨导致这一现象的主要原因&#…

颅内感染性疾病患者就诊指南

颅内感染性疾病&#xff0c;即病原体侵入中枢神经系统&#xff0c;导致脑部或脑膜发生炎症的疾病。这些病原体可能是细菌、病毒、真菌或寄生虫等。颅内感染不仅会对脑组织造成损害&#xff0c;还可能引发一系列严重的并发症&#xff0c;如癫痫发作、意识障碍等 颅内感染性疾病的…

国产软件号称Windows系统的天花板,却被误认为是外国佬研发

说起国产软件&#xff0c;大家总是容易给它们贴上“流氓、捆绑、满满的都是套路”这样的标签。 其实挺冤枉的&#xff0c;有些软件真的挺好用&#xff0c;也挺良心的&#xff0c;但就是因为这些刻板印象&#xff0c;老是被误以为是外国工程师搞出来的。 VeryCapture 之前小编…

JavaScript之深入对象,详细讲讲构造函数与常见内置构造函数

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;我是前端菜鸟的自我修养&#xff01;今天给大家详细讲讲构造函数与常见内置构造函数&#xff0c;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;原创不易&#xff0c;如果能帮助到带大家&#xff0c;欢迎…

达梦数据库的系统视图v$deadlock_history

达梦数据库的系统视图v$deadlock_history 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;V$DEADLOCK_HISTORY 视图记录了数据库中发生的死锁信息。通过查询这个视图&#xff0c;数据库管理员可以监控和诊断数据库中的死锁问题&#xff0c;从而采取相应的措施…

鸿蒙认证值得考吗?

鸿蒙认证值得考吗&#xff1f; 鸿蒙认证&#xff08;HarmonyOS Certification&#xff09;是华为为了培养和认证开发者在鸿蒙操作系统&#xff08;HarmonyOS&#xff09;领域的专业技能而设立的一系列认证项目。这些认证旨在帮助开发者和企业工程师提升在鸿蒙生态中的专业技能…

小故事——半个世纪的爱情

半个世纪的爱情 故事的开端永远是在那个情窦初开的年纪&#xff0c;那富有蓬勃朝气的少年时代&#xff0c;眼神中青涩未尽&#xff0c;正是这个时间&#xff0c;才真正的让人难以忘怀。她不过是那班级里面普普通通的小孩&#xff0c;故事的男主角同样也是简简单单的存在&#…

激光SLAM如何动态管理关键帧和地图

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

Activity、Window、DecorView的关系

目录 一、Activity、Window、DecorView的层级关系如下图所示&#xff1a; 1、Activity 2、Window 3、DecorView 二、DecorView初始化相关源码 三、DecorView显示时机 前言&#xff1a; 不同的Android版本有差异&#xff0c;以下基于Android 11进行讲解。 一、Activi…

音乐发行平台无加密开源源码

适用于唱片公司&#xff0c;用于接收物料&#xff0c;下载物料功能&#xff1a;个人或机构认证&#xff0c;上传专辑和歌曲&#xff0c;版税结算环境要求php7.4Nginx 1、导入数据库 2、/inc/conn.php里填写数据库密码等后台路径/admin&#xff08;可自行修改任意入口名称&…

Meta 3D Gen:文生 3D 模型

是由 Meta 公布的一个利用 Meta AssetGen&#xff08;模型生成&#xff09;和 TextureGen&#xff08;贴图材质生成&#xff09;的组合 AI 系统&#xff0c;可以在分分钟内生成高质量 3D 模型和高分辨率贴图纹理。 视频演示的效果非常好&#xff0c;目前只有论文&#xff0c;期…

计算机网络--网络层

一、网络层的服务和功能 网络层主要为应用层提供端对端的数据传输服务 网络层接受运输层的报文段&#xff0c;添加自己的首部&#xff0c;形成网络层分组。分组是网络层的传输单元。网络层分组在各个站点的网络层之间传输&#xff0c;最终到达接收方的网络层。接收方网络层将运…

PLC_博图系列☞TP:生成脉冲

PLC_博图系列☞TP&#xff1a;生成脉冲 文章目录 PLC_博图系列☞TP&#xff1a;生成脉冲背景介绍TP&#xff1a; 生成脉冲说明参数脉冲时序图示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 TP 背景介绍 这是一篇关于PLC编程的文章&#xff0c;特别是关于西门…