常见的锁策略与死锁(详解)

news/2024/10/18 10:13:59/

文章目录

  • 前言
  • 一、常见的锁策略
    • 1.乐观锁vs悲观锁
    • 2.重量级锁vs轻量级锁
    • 3.自旋锁vs挂起等待锁
    • 4.读写锁vs互斥锁
    • 5.公平锁vs非公平锁
    • 6.可重入锁vs不可重入锁
      • 可重入锁在哪释放锁
    • 7.synchronized具体是采用了哪些锁策略呢?
      • synchronized内部实现策略(内部原理)
      • 锁消除
      • 锁粗化
  • 二、死锁
    • 1.什么是死锁
    • 2.死锁的几个典型的场景
    • 3.死锁产生的必要条件
    • 4.如何解决死锁的问题
  • 三、ReentrantLock可重入锁


前言


一、常见的锁策略

指的不是某个具体的锁。抽象的概念,描述的是锁的特性,描述的是“一类锁"
锁冲突:两个线程尝试获取一把锁,一个线程能获取成功,另一个线程阻塞等待。

1.乐观锁vs悲观锁

1.乐观锁:预测该场景中,不太会出现锁冲突的情况.(后续做的工作会更少)
2.悲观锁:预测该场景,非常容易出现锁冲突.(后续做的工作会更多)

2.重量级锁vs轻量级锁

1.重量级锁:加锁的开销是比较大的(花的时间多,占用系统资源多)
一个悲观锁,很可能是重量级锁(不绝对)
2.轻量级锁:加锁开销比较小的.(花的时间少,占用系统资源少)
一个乐观锁,也很可能是轻量级锁(不绝对)

3.自旋锁vs挂起等待锁

1.自旋锁,是轻量级锁的一种典型实现.
在用户态下,通过自旋的方式(while循环).实现类似于加锁的效果的.
这种锁,会消耗一定的cpu资源,但是可以做到最快速度拿到锁~~

2.挂起等待锁,是重量级锁的一种典型实现.
通过内核态,借助系统提供的锁机制,当出现锁冲突的时候,会牵扯到内核对于线程的调度. 使冲突的线程出现挂起(阻塞等待)
这种方式,消耗的cpu资源是更少的~~也就无法保证第一时间拿到锁.

4.读写锁vs互斥锁

1.读写锁,把读操作加锁和写操作加锁分开了.
一个事实:多线程同时去读同一个变量,不涉及到线程安全问题(此时,多线程并发执行的效率就更高)
如果两个线程,一个线程读加锁,另一个线程也是读加锁,不会产生锁竞争.
如果两个线程,一个线程写加锁,另一个线程也是写加锁,会产生锁竞争.
如果两个线程,一个线程写加锁,另一个线程读加锁,也会产生锁竞争.

5.公平锁vs非公平锁

1.公平锁,是遵守先来后到的锁
2.非公平锁,看起来是概率均等,但是实际上是不公平.(每个线程阻塞时间是不一样的)

操作系统自带的锁(pthread mutex)属于是非公平锁
要想实现公平锁,就需要有一些额外的数据结构来支持.(比如需要有办法记录每个线程的阻塞等待时间)

6.可重入锁vs不可重入锁

如果一个线程,针对一把锁,连续加锁两次,会出现死锁,就是不可重入锁;不会出现死锁,就是可重入锁

在这里插入图片描述

如果是不可重用锁就会出现:死锁!!
这里的关键在于,两次加锁,都是“同一个线程".
第二次尝试加锁的时候,该线程已经有了这个锁的权限了。这个时候,不应该加锁失败的,不应该阻塞等待的。

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

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

synchronized 本身是一个可重入锁
在这里插入图片描述
上述代码并不会出现死锁。

可重入锁在哪释放锁

在这里插入图片描述
如何判断应该释放锁了呢?
让锁这里持有一个“计数器"就行了。
让锁对象不光要记录是哪个线程持有的锁,同时再通过一个整型变量记录当前这个线程加了几次锁!!
每遇到一个加锁操作,就计数器+1,每遇到一个解锁操作,就–1
当计数器被减为0的时候,才真正执行释放锁操作.其他时候不释放。“引用计数”

7.synchronized具体是采用了哪些锁策略呢?

  1. synchronized 既是悲观锁,也是乐观锁. (自适应)
  2. synchronized既是重量级锁,也是轻量级锁. (自适应)
  3. synchronized重量级锁部分是基于系统的互斥锁实现的;轻量级锁部分是基于自旋锁实现的
  4. synchronized是非公平锁(不会遵守先来后到.锁释放之后,哪个线程拿到锁,各凭本事)
  5. synchronized是可重入锁. (内部会记录哪个线程拿到了锁,记录引用计数)
  6. synchronized不是读写锁.

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

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

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

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

2.轻量级锁:sychronized通过自旋锁的方式来实现轻量级锁。
我这边把锁占据了,另一个线程就会按照自旋的方式,来反复查询当前的锁的状态是不是被释放了.
但是,后续,如果竞争这把锁的线程越来越多了(锁冲突更激烈了),从轻量级锁(这个锁操作是比较消耗cpu的如果能够比较快速的拿到锁,多消耗点CPU也不亏.)升级成重量级锁(但是,随着竞争更加激烈即使前一个线程释放锁,也不一定能拿到锁.啥时候能拿到,时间可能会比较久了)。

锁消除

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

锁粗化

关于"锁的粒度"
如果加锁操作里包含的实际要执行的代码越多,就认为锁的粒度越大
在这里插入图片描述

二、死锁

1.什么是死锁

如果是一个服务器程序,出现死锁,
死锁的线程就僵住了,就无法继续工作了,会对程序造成严重的影响

2.死锁的几个典型的场景

死锁的三种典型情况:
1.一个线程,一把锁,但是是不可重入锁.该线程针对这个锁连续加锁两次,就会出现死锁
⒉两个线程,两把锁.这两个线程先分别获取到一把锁,然后再同时尝试获取对方的锁

2的案例:
在这里插入图片描述
在这里插入图片描述
结果:
在这里插入图片描述
在这里插入图片描述
3.N个线程M把锁(哲学家就餐问题)
5个哲学家,就是5个线程。5个筷子,就是5把锁

在这里插入图片描述
但是,如果出现了极端情况,就会出现死锁。
比如,同一时刻,五个哲学家都想吃面,并且同时伸出左手拿起左边的筷子,再尝试伸右手拿右边的筷子。

3.死锁产生的必要条件

死锁的必要条件:(四个必要条件:缺一不可)
只要能够破坏其中的任意一个条件,都可以避免出现死锁!
1.互斥使用:一个线程获取到一把锁之后,别的线程不能获取到这个锁。
实际使用的锁,一般都是互斥的(锁的基本特性)
2.不可抢占锁,只能是被持有者主动释放,而不能是被其他线程直接抢走。
也是锁的基本的特性.
3.请求和保持.这个一个线程去尝试获取多把锁,在获取第二把锁的过程中,会保持对第一把锁的获取状态。
取决于代码结构(很可能会影响到需求)
4.循环等待. t1尝试获取locker2,需要t2执行完,释放locker2; t2尝试获取locker1,需要t1执行完,释放locker1
取决于代码结构(解决死锁问题的最关键要点~~)

4.如何解决死锁的问题

如果具体解决死锁问题,实际的方法有很多种。(如银行家算法,可以解决死锁问题,但是不太接地气)

介绍一个,更简单有效的解决死锁的方法:针对锁进行编号.并且规定加锁的顺序。
比如,约定,每个线程如果要获取多把锁,必须先获取编号小的锁,后获取编号大的锁.
只要所有线程加锁的顺序,都严格遵守上述顺序,就一定不会出现循环等待!!


三、ReentrantLock可重入锁

这个锁,没有synchronized那么常用,但是也是一个可选的加锁的组件.

lock()加锁
unlock()解锁
在这里插入图片描述
ReentrantLock 具有一些特点,是synchronized 不具备的功能:
在这里插入图片描述
实际开发中,进行多线程开发,用到锁还是首选 synchronized


http://www.ppmy.cn/news/1440767.html

相关文章

【bug】使用mmsegmentaion遇到的问题

利用mmsegmentaion跑自定义数据集时的bug处理(使用bisenetV2) 1. ValueError: val_dataloader, val_cfg, and val_evaluator should be either all None or not None, but got val_dataloader{batch_size: 1, num_workers: 4}, val_cfg{type: ValLoop}, …

HTTP和SOCKS代理的区别及应用场景解析

HTTP代理: HTTP代理是基于HTTP协议的代理服务器,主要用于代理浏览器的访问。它在应用层上运行,仅允许用户通过HTTP协议访问外部站点。HTTP代理通常使用标准端口80、8080、3128等,常见的代理类型包括HTTP和HTTPS代理。 HTTP代理适…

在线音乐播放网站项目测试(selenium+Junit5)

在做完在线音乐播放网站项目之后,需要对项目的功能、接口进行测试,利用测试的工具:selenium以及Java的单元测试工具Junit进行测试,下面式测试的思维导图,列出该项目需要测试的所有测试用例: 测试结果&#…

微信收款码0.2费率开通

很多人想申请低手续费率的收款码不知从何下手,在参考了大量博客教学之后,终于搞懂了详细流程以及注意事项。在此记录一下。我申请的是一个只需要0.2%费率的微信收款码,申请时间是2022年2月12日。申请之前只需要准备营业执照和法人身份z&#…

ArcGIS Pro 和 Python — 分析全球主要城市中心的土地覆盖变化

第一步——设置工作环境 1–0. 地理数据库 在下载任何数据之前,我将创建几个地理数据库,在其中保存和存储所有数据以及我将创建的后续图层。将为我要分析的五个城市中的每一个创建一个地理数据库,并将其命名为: “Phoenix.gdb” “Singapore.gdb” “Berlin.gdb” “B…

【BUG】Hexo|GET _MG_0001.JPG 404 (Not Found),hexo博客搭建过程图片路径正确却找不到图片

我的问题 我查了好多资料,结果原因是图片名称开头是_则该文件会被忽略。。。我注意到网上并没有提到这个问题,遂补了一下这篇博客并且汇总了我找到的所有解决办法。 具体检查方式: hexo生成一下静态资源: hexo g会发现这张图片…

【国标语音对讲】EasyCVR视频汇聚平台海康/大华/宇视摄像头GB28181语音对讲配置

一、背景分析 近年来,国内视频监控应用发展迅猛,系统接入规模不断扩大,涌现了大量平台提供商,平台提供商的接入协议各不相同,终端制造商需要给每款终端维护提供各种不同平台的软件版本,造成了极大的资源浪…

大数据第六天

这里写目录标题 问题解决问题查询插入(时间慢)练习sql数据清理 问题 FAILED: ParseException line 1:16 mismatched input ‘input’ expecting INPATH near ‘local’ in load statement MismatchedTokenException(24!155) 加载数据的时候出现了这个错误,我们解释…