当前位置: 首页 > news >正文

多线程 2 - 死锁问题

死锁

死锁,是多线程代码中的一类经典问题。加锁能够解决线程安全问题,但如果加锁方式不当,就很可能产生死锁。

出现死锁的三种场景

1、一个线程一把锁

就像上篇文章讲过的,如果对同一个线程上了两把锁,而且上的锁是不可重入锁的话,就会出现死锁问题。

2、两个线程两把锁

线程1获取锁A,同时线程2获取锁B,接下来,线程1尝试获取锁B,线程2尝试获取锁A。此时,就同样出现死锁了,一旦出现死锁,进程就会卡住,无法继续工作。死锁,是属于进程中的最严重的一种bug!!!

代码示例:

public class ThreadDemo22 {public static void main(String[] args) {Object A = new Object();Object B = new Object();Thread t1 = new Thread(()->{//sleep一下,是为了给t2时间,让t2也获取到锁synchronized (A){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//不释放A尝试获取Bsynchronized (B){System.out.println("t1获得了两把锁");}}});Thread t2 = new Thread(()->{synchronized (B){//给t1时间,让t1也获取到锁try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//尝试获取A,没有释放Bsynchronized (A){System.out.println("t2获得了两把锁");}}});t1.start();t2.start();}
}

点击运行,可以看到什么都没有打印:

通过jconsole进行观察 :

Thread-0被Thread-1锁住了,状态为BLOCKED;Thread-1也被Thread-0锁住了,状态为BLOCKED。

3、N个线程M把锁

这种情况就会涉及到“哲学家就餐”的问题。

有五个哲学家围坐在一张圆桌旁边,他们需要做的事就是思考和进餐。圆桌上有五根筷子,每两个哲学加之间放一根。当科学家需要进餐时,必须要同时拿起左右两边的筷子。如果筷子已经被其他科学家占用,那么当前要进餐的科学家必须等待,直到筷子可用。

也就是说,当某个哲学家在吃面条的过程中,他旁边的两位科学家,就需要进行阻塞等待(五个筷子就相当于五把锁,每个科学家,就相当于一个进程)。当线程拿到锁的时候,就会一直持有这把锁,除非它释放了锁(哲学家吃完了,主动放下了筷子)。在这个线程拿到这把锁的时候,其他线程不能获取到这把锁(哲学家都是有身份的人,不能硬抢筷子)。

虽然筷子数量并不充裕,但是,每个科学家,除了吃面条之外,还需要思考人生,在思考人生时,科学家是会放下筷子的。

由于每个哲学家,什么时候吃面条,什么时候思考人生,这个事情是不确定的(随机调度) ,绝大多数情况下,上述模型都是能够正常工作的。

但是,有一些极端的特殊情况,是无法正常工作的。

假设同一时刻,所有哲学家,都想吃面条,同时拿起了左手的筷子,这时,他们尝试拿右边的筷子。诶,发现拿不起来了(因为右边的筷子给别的科学家拿了)。

此时,由于所有的哲学家,都不想放下已经拿起来的筷子,就要等待旁边的人放下筷子,但是由于没有人吃到面,也就没有人放下筷子(当前每个线程锁里的任务还没有完成,也就没有线程释放锁,此时也就变成死锁了)。

产生死锁的四个必要条件

1、互斥使用:获取锁的过程是互斥的,一个线程拿到了这把锁,另一个线程也想获取就需要阻塞等待。

2、不可抢占:一个线程拿到锁之后,只能主动解锁,不能让别的线程,强行把锁抢走。

3、请求保持:一个线程在拿到A之后,在持有A的前提下,尝试获取B。

4、循环等待/环路等待: 就像上面的情况,五个哲学家同时想吃饭的时候,同时拿起了左边的筷子。

解决死锁问题

解决死锁问题就需要破坏上述四种必要条件的其中一个。

1、互斥使用。这是锁的基本特性,我们很难破坏。

2、不可抢占。这也是锁的特性,我们很难破坏。

3、请求保持。这个操作是取决于代码结构的,不一定可以破坏,要看实际代码需求。

4、循环等待/环路等待。这个是对比其他三个,最好破坏的。

我们可以通过指定加锁顺序,针对五把锁和五个哲学家,都进行编号,约定当每个哲学家(线程)拿筷子时候(获取锁的时候),必须要先获取编号小的筷子(锁),再获取编号大的筷子(锁)。

如果我们指定了加锁顺序,也就相当于指定了哲学家拿筷子的顺序,哲学家只能拿自己面前的两只筷子,且要先拿编号小的筷子。

那么,从2号哲学家开始,当他拿筷子的时候,眼前的1、2两只筷子,他需要先拿1筷子: 

2号哲学家拿完后,轮到3号哲学家,因为我们指定要拿编号小的筷子,所以他要拿的是2号筷子 同理,4、5号这学家要拿3、4号筷子。

但轮到1号哲学家时,因为我们指定要先拿编号小的筷子,即1号哲学家,要先获取1号筷子,但是此时1号筷子在2号哲学家手上,1号哲学家就只能等着了(阻塞等待)。此时5号筷子时空闲的,因此5号哲学家,就可以拿5号筷子,和4号筷子组成一双筷子吃面了!!!当5号哲学家吃完后,放下4、5号筷子,4号哲学家就可以吃面了,依此类推,3号、2号、1号哲学家都能吃上面了。

因此,对于刚才出现死锁的代码,我们只需要让t2线程先获取A锁再获取B锁就可以解决死锁问题了。

代码如下:


public class ThreadDemo22 {public static void main(String[] args) {Object A = new Object();Object B = new Object();Thread t1 = new Thread(()->{//sleep一下,是为了给t2时间,让t2也获取到锁synchronized (A){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//不释放A尝试获取Bsynchronized (B){System.out.println("t1获得了两把锁");}}});Thread t2 = new Thread(()->{synchronized (A){//给t1时间,让t1也获取到锁try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//尝试获取A,没有释放Bsynchronized (B){System.out.println("t2获得了两把锁");}}});t1.start();t2.start();}

运行结果: 

 那我们能不能指定编号小的哲学家先吃面呢?(编号小的线程先上锁)

这是不可行的,因为线程都是“随机调度”、“抢占式执行的”,想办法让某个线程先加锁,违背了“随机调度的原则”,可行性是不高的。而约定加锁顺序,在写代码的时候是非常容易做到的。

解决死锁的其他方法

1、引入额外的筷子:引入额外的锁。

2、去掉一个线程。

3、引入计数器,限制最多同时存在多少个线程。

上面三种方案,其实现并不难,但它们的适用性不高。

4、刚才讲过的引入加锁顺序的方法(普适性较高,且容易实现)。

5、学校的操作系统课中会有一种“银行家算法”,这个方案,确实可以可以解决死锁问题,但我们在实际开发中,一般不会这么做。因为这种方法实在太复杂了,为了解决死锁问题,实现“银行家算法”……死锁解没解决不确定,搞不好,我们在实现“银行家算法”的过程中,就bug满天飞了!!!(也就是说这种方法,理论上可行,实际中并不推荐)。

 

相关文章:

  • c#建筑行业财务流水账系统软件可上传记账凭证财务管理系统签核功能
  • MindSpore框架学习项目-ResNet药物分类-模型优化
  • CSS渲染性能优化
  • STM32实现九轴IMU的卡尔曼滤波
  • 阿里云购买ECS 安装redis mysql nginx jdk 部署jar 部署web
  • STM32-ADC模数转换器(7)
  • 数据链共享:从印巴空战到工业控制的跨越性应用
  • Axure :基于中继器的列表删除 、 列表编辑
  • 深入理解 TCP:重传机制、滑动窗口、流量控制与拥塞控制
  • arXiv2025 | TTRL: Test-Time Reinforcement Learning
  • CDGP数据治理主观题评分标准与得分策略
  • Linux平台下SSH 协议克隆Github远程仓库并配置密钥
  • ui组件二次封装(vue)
  • Android 关闭Activity切换过渡动画
  • uniapp-商城-50-后台 商家信息
  • C++ 命令模式详解
  • .Net Mqtt协议-MQTTNet(一)简介
  • Ubuntu22.04怎么退出Emergency Mode(紧急模式)
  • 【许可证】Open Source Licenses
  • 两个数组的交集(暴力、set、哈希)
  • 重庆荣昌出圈背后:把网络流量变成经济发展的增量
  • 以总理内塔尼亚胡称决心彻底击败哈马斯
  • 央行:全力推进一揽子金融政策加快落地
  • 湖南张家界警方公告宣布一名外国人居留许可作废
  • 印度导弹凌晨打击巴基斯坦多座设施,巴总理:正对战争行为作有力回应
  • 中国驻俄大使张汉晖人民日报撰文:共襄和平伟业,续谱友谊新篇