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

文章目录

  • 前言
  • 一、常见的锁策略
    • 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.hqwc.cn/news/661494.html

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

相关文章

int类型的取值范围(为什么负数比正数表示的范围多一位)

🎁个人主页:我们的五年 🔍系列专栏:C语言基本概念 🌷追光的人,终会万丈光芒 目录 🏝1.int的基本概念: 空间大小: 有符号类型的表示形式: 🏝2.…

《MySQL对库的基本操作》

文章目录 一、查看数据库列表查看数据库中的所有表想知道当前处于哪个数据库里 二、创建一个数据库三、删除一个数据库知道两个集1.字符集2.校验集修改数据库的字符集和编码集 不同的校验码对数据库的影响四、数据库的备份与恢复注意事项:备份数据库中的表 总结 一、…

纯血鸿蒙APP实战开发——发布图片评论

介绍 本示例将通过发布图片评论场景,介绍如何使用startAbilityForResult接口拉起相机拍照,并获取相机返回的数据。 效果图预览 使用说明 通过startAbilityForResult接口拉起相机,拍照后获取图片地址。 实现思路 创建CommentData类&#…

GPU 架构与 CUDA 关系 并行计算平台和编程模型 CUDA 线程层次结构 GPU 的算力是如何计算的 算力峰值

GPU 架构与 CUDA 关系 本文主要包含 NVIDIA GPU 硬件的基础概念、CUDA(Compute Unified Device Architecture)并行计算平台和编程模型,详细讲解 CUDA 线程层次结构,最后将讲解 GPU 的算力是如何计算的,这将有助于计算大模型的算力峰值和算力利用率。 GPU 硬件基础概念GP…

目标检测算法YOLOv3简介

YOLOv3由Joseph Redmon等人于2018年提出,论文名为:《YOLOv3: An Incremental Improvement》,论文见:https://arxiv.org/pdf/1804.02767.pdf ,项目网页:https://pjreddie.com/darknet/yolo/ 。YOLOv3是对YOL…

【Unity动画系统】动画状态转换详解

动画状态转换 此空处可以改换新转换名字。 表示有多个转换,播放顺序不可调整。 Solo:表示只执行它们,其他没勾选的不考虑;都勾选了,哪个转换条件先满足,就先执行哪个转换;如果同时满足,那就按顺序执行。 M…

无人机+三维建模:倾斜摄影技术详解

无人机倾斜摄影测量技术是一项高新技术,近年来在国际摄影测量领域得到了快速发展。这种技术通过从一个垂直和四个倾斜的五个不同视角同步采集影像,从而获取到丰富的建筑物顶面及侧视的高分辨率纹理。这种技术不仅能够真实地反映地物情况,还能…

Redis源码学习记录:列表 (ziplist)

ziplist redis 源码版本&#xff1a;6.0.9。ziplist 的代码均在 ziplist.c / ziplist.h 文件中。 定义 ziplist总体布局如下&#xff1a; <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend> zlbytes&#xff1a;uin…

深入解析智能指针:从实践到原理

&#x1f466;个人主页&#xff1a;晚风相伴 &#x1f440;如果觉得内容对你有所帮助的话&#xff0c;还请一键三连&#xff08;点赞、关注、收藏&#xff09;哦 如果内容有错或者不足的话&#xff0c;还望你能指出。 目录 智能指针的引入 内存泄漏 RAII 智能指针的使用及原…

【Linux 系统】多线程(线程控制、线程互斥与同步、互斥量与条件变量)-- 详解

一、线程概念 线程是进程的一个执行分支&#xff0c;是在进程内部运行的一个执行流。下面将从是什么、为什么、怎么办三个角度来解释线程。 1、什么是线程 上面是一张用户级页表&#xff0c;我们都知道可执行程序在磁盘中无非就是代码或数据&#xff0c;更准确点表述&#xff0…

OpenSceneGraph

文章目录 关于 OpenSceneGraphScreenshots - OpenMW 关于 OpenSceneGraph 官网&#xff1a;https://openscenegraph.github.io/openscenegraph.io/github : https://github.com/openscenegraph/OpenSceneGraphClasses : https://podsvirov.github.io/osg/reference/opensceneg…

Linux 文件管理命令Lawk wc comm join fmt

文章目录 2.Linux 文件管理命令2.44 awk&#xff1a;模式匹配语言1&#xff0e;变量2&#xff0e;运算符3&#xff0e;awk 的正则4&#xff0e;字符串函数5&#xff0e;数学函数案例练习 2.45 wc&#xff1a;输出文件中的行数、单词数、字节数案例练习2.46 comm&#xff1a;比较…