常见锁策略之可重入锁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.mzph.cn/diannao/39780.shtml

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

相关文章

前端基础--Vue3

Vue3基础 VUE3和VUE2的区别 2020年9月18日,Vue.js发布版3.0版本,代号:One Piece 于 2022 年 2 月 7 日星期一成为新的默认版本! Vue3性能更高,初次渲染快55%, 更新渲染快133% 。体积更小 Vue3.0 打包大小减少41%。 同时Vue3可以更好的支持T…

基于微服务智能推荐健康生活交流平台的设计与实现(SpringCloud SpringBoot)+文档

💗博主介绍💗:✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示:文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

vb 学习简介

vb 第一节 Visual Basic(简称VB)是一种高级编程语言,它最初由微软公司开发,旨在简化Windows应用程序的开发过程。下面,我们将介绍Visual Basic编程语言的基础概念和用途,包括其历史背景、主要特性以及在现代编程中的应用。 历史背景 Visual Basic起源于1991年,当时微软…

代码随想录算法训练营day72 | 117. 软件构建、47. 参加科学大会

本次题目来自于卡码网 117. 软件构建(拓扑排序) python设置默认值 from collections import defaultdict aa defaultdict(int) 拓扑排序:找到入度为0的节点,然后移除。如果最后都能移除,则无环,可以排…

C#发票识别接口,再长的税号录入都不怕

“十二金”工程是我国政府在信息化建设中的重要一步,“金税工程”总称为中国税收管理信息系统(CTAIS),是我国电子政务的核心系统之一,是财政的重要环节。十二金”是面向政府办公业务建立的十二个重点信息应用系统,按“…

解决使用monaco-editor编译器,编译器展示内容没有超过编译器高度,但是出现滚动条问题

前言: 最近在完成项目时,有使用编译器进行在线编辑的功能,就选用了monaco-editor编译器,但是实现功能之后,发现即使在编译器展示的内容没有超过编译器高度的情况下,编译器依旧存在滚动条,会展示…

计算机网络--网络层

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

如何在 Java 应用中使用 Jedis 客户端库来实现 Redis 缓存的基本操作

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

构建高效盲盒小程序:数据库设计、安全策略与性能优化

在移动互联网时代,盲盒经济以其独特的魅力迅速崛起,成为连接消费者与商品的新桥梁。盲盒小程序作为这一趋势的载体,不仅要求用户体验流畅,还需确保数据安全与性能卓越。本文将从数据库设计、安全策略及性能优化三个方面&#xff0…

堆与栈的概念(RTOS)

目录 #堆在RTOS的概念 #相关代码表示 #堆相关特点 #栈在RTOS中的概念 #栈的代码表示 #栈的相关特点 #为什么每个RTOS任务都要有自己的栈 前言:本篇参考韦东山老师的RTOS,连接放在最后 #堆在RTOS的概念 本文所指的堆与栈并不是数据结构中&#xff…

【unity实战】在Unity中使用有限状态机制作一个敌人AI

最终效果 文章目录 最终效果前言有限状态机的主要作用和意义素材下载逻辑图敌人动画配置优雅的代码文件目录状态机代码定义敌人不同状态切换创建敌人效果更多的敌人参考源码完结 前言 有限状态机以前的我嗤之以鼻,现在的我逐帧分析。其实之前我就了解过有限状态机&…

2.(vue3.x+vite)调用iframe的方法(vue编码)

1、效果预览 2.编写代码 (1)主页面 <template><div><button @click="sendMessage">调用iframe,并发送信息

【udp报文】udp报文未自动分片,报文过长被拦截问题定位

问题现象 某局点出现一个奇怪的现象&#xff0c;客户端给服务端发送消息&#xff0c;服务端仅能收到小部分消息&#xff0c;大部分消息从客户端发出后&#xff0c;服务端都未收到。 问题定位 初步分析 根据现象初步分析&#xff0c;有可能是网络原因导致消息到服务端不可达&a…

【C语言】文件的顺序读写

©作者:末央&#xff06; ©系列:C语言初阶(适合小白入门) ©说明:以凡人之笔墨&#xff0c;书写未来之大梦 目录 前言字符输入输出函数 - fgetc和fputc文本行输入输出函数 - fgets和fputs格式化输入输出函数 - fscanf和fprintf 前言 对文件数据的读写可以分为顺序…

Unity3D 打造基于AStar的寻路与导航详解

在游戏开发中&#xff0c;寻路与导航是一个至关重要的功能&#xff0c;它能够使游戏角色自动找到最优路径&#xff0c;避开障碍物&#xff0c;实现自动导航&#xff0c;从而提升游戏体验。AStar&#xff08;A*&#xff09;算法作为一种广泛应用的寻路算法&#xff0c;因其高效性…

关于多线程的使用方法

多线程在python中应用比较广泛&#xff0c;但是因为python中有GIL锁的缘故&#xff0c;在多线程中看起来是并发的执行的&#xff0c;在宏观上是并发执行的&#xff0c;但是在微观上是一个接着一个执行。 在python中使用多线程比较简单&#xff0c;是一套固定的模版。 from qu…

PHP利用GD库实现图片合成功能方法

在程序项目开发的过程中我们免不了要实现一种功能。例如海报的生成&#xff0c;照片和文字合成一张新的图片。php中怎么实现 实现功能 文字和照片合成一张新的照片&#xff0c;并且自适应换行并加上签名和日期&#xff0c;加上字体样式&#xff0c;下面我们就开实现该功能 实现…

Seal^_^【送书活动第8期】——《ChatGLM3大模型本地化部署、应用开发与微调》

Seal^_^【送书活动第8期】——《ChatGLM3大模型本地化部署、应用开发与微调》 一、参与方式二、本期推荐图书2.1 作者建语2.2 编辑推建2.3 图书简介2.4 前 言2.5 目 录 三、正版购买 大模型领域 既是繁星点点的未知宇宙&#xff0c;也是蕴含无数可能的广阔天地&#xff0c; 正…

深入理解 Linux 内核架构

目录 引言内核概念Linux 内核的基本组成 进程管理内存管理文件系统设备驱动网络栈内核结构 内核态与用户态内核模块系统调用中断与异常处理内核同步机制Linux 内核使用场景常用的内核命令与工具内核调试与性能优化总结 1. 引言 Linux 内核是现代计算机系统的核心组件之一&am…

python--基础知识点--协程

协程由用户态控制&#xff0c;不由内核控制1个线程中可以开很多协程协程切换是在用户态控制不由内核控制&#xff0c;切换时资源开销小使用方式&#xff1a;async def、await可等待对象(协程对象、Future对象、task对象(是Future对象的子类)->io等待)、事件循环使用场景&…