关于我对线程安全问题中死锁的理解
创始人
2024-03-13 04:14:53
0

文章目录

  • 1.什么是死锁
  • 2.三个典型情况
  • 3.可重入与不可重入
  • 4.死锁的四个必要条件
  • 5.如何破除死锁

1.什么是死锁


比如张三谈了一个女朋友,张三就对这个女朋友加锁了。
此时李四也看上了这个女生,但是他只能等待张三分手(解锁)后,才能和这个女生谈恋爱。

李四为了等待这个女生,错过了好多喜欢他的人,这里就相当于线程无法执行的后续工作,
此时就相当于是死锁了。

一旦程序出现死锁,就会导致线程无法执行后序工作了,此时程序必然会有严重的 bug 。
发生死锁的概率又是随机的,因此死锁是非常隐蔽,不容易被发现的。

2.三个典型情况

情况1:

一个线程如果有一把锁,连续加锁两次。如果这个锁是不可重入锁,就会死锁。

java 中的 synchronizedReentrantLock 都是可重入锁,
因此这一种情况演示不了。


情况2:

两个线程两把锁,t1 和 t2 各自先针对 锁1 和 锁2 加锁,之后再尝试获取对方的锁。

比如说,张三的车钥匙锁在屋里了,而屋子的钥匙锁在车了。
这个时候屋子和车都进不去了,就会产生问题。

下面来举例说明。

package thread;public class ThreadDemo16 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2) {System.out.println("线程t1拿到两个锁");}}});Thread t2 = new Thread(() -> {synchronized (locker2) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker1) {System.out.println("线程t2拿到两个锁");}}});t1.start();t2.start();}
}


这里并没有输出结果,说明线程并没有拿到两把锁。

这个时候可以使用 jconsole 来查看当前的进程的情况。


按照这样的路径查找 jconsole ,然后双击。



看到这样的窗口,双击选中的进程。






红色框框的表示 获取锁获取不到的阻塞状态
绿色框框的表示 发生错误的代码行数






情况3: 多个线程,多把锁。(向较与情况2的一半情况

例子:哲学家就餐问题



每个哲学家有两种状态:

  1. 思考人生(相当于线程的阻塞状态
  2. 拿起筷子吃面条(想当于线程获取到锁然后执行一些操作)、

由于操作系统的随机调度,这五个哲学家,随时都可能想吃面条,也随时可能要思考人生。

如果想吃面条就需要拿起左手和右手的筷子。


假如同一时刻,所有的哲学家都拿起左手的筷子吃面条。
此时如果要成功吃到面条,就要等到右边的哲学家放下手中的筷子,自己才可以吃到面条。
此时就会死锁!!!

只有一只筷子没有办法吃面条,必须要等到右边的老铁放下筷子才可以吃。
如果右边一直不放,左边的老铁就一直吃不到。

3.可重入与不可重入

一个线程针对同一个对象,如果不会发生问题就叫可重入的,否则就叫不可重入的。

class Counter {public int count = 0;synchronized public void add() {synchronized(this) {count++;}}
}

锁对象是 this ,只要有线程调用 add 方法,进入 add 方法的时候,
在可以加锁成功的前提下,就会先加锁。紧接着又遇到了代码块,此时会再次尝试加锁。

站在 锁对象的视角(this),他认为自己已经被其他的线程给占用了,
那么这里的第二次加锁是不需要阻塞等待的。

如果允许上述操作,这个锁就是可重入的;不允许就是不可重入的。
如果是不可重入的,就会发生 死锁。

上面演示的就是不可重入的死锁。


下面演示的是可重入的思索。

java 为了避免不小心出现闭锁现象,就把 synchronized 给设置成可重入的了。
因此 java 中才会无法演示上面的情况1.

4.死锁的四个必要条件

1、互斥使用 — 线程1拿到了锁,线程1就需要等待着。

2、不可抢占 — 线程1拿到锁之后,如果线程1不释放锁,线程2就不能强行获取。

3、请求和等待 — 线程1获取到锁A之后,再去获取到锁B,
此时锁A还会继续被线程1获取。(不会因为获取锁B后就把锁A给释放了)

4、循环等待 — 线程1尝试获取到锁A锁B,线程2尝试获取到锁B锁A
线程1在获取B的时候等待线程2释放B,同时线程2在获取A的时候等待线程1释放A

5.如何破除死锁

打破循环等待这个必要条件。

解决办法:

给每个筷子编号,指定固定的顺序(从小到大)拿筷子。


上图是规定从小到大的拿。


到最后一个老铁拿的时候,会拿一号筷子。
但是这个一号筷子被其他的老铁拿了,此时这个老铁就发生阻塞等待了。
此时拿四号筷子的老铁会把五号筷子也拿了,之后开始吃面条。


这个老铁吃面条的时候,拿三号筷子的老铁就会看着他吃。
等待这个老铁吃完,放下两支筷子号筷子,三号筷子的老铁就可以拿起四号筷子来吃了。


按照这样的方式,所有的老铁都可以吃面条。


下面由代码来演示:

package thread;public class ThreadDemo16 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();//给两个锁编号:1 、2,规定locker1是1号,locker2是2号,按照从小到大的顺序拿Thread t1 = new Thread(() -> {//先拿序号小的synchronized (locker1) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//后拿序号大的synchronized (locker2) {System.out.println("线程t1拿到两个锁");}}});Thread t2 = new Thread(() -> {synchronized (locker1) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2) {System.out.println("线程t2拿到两个锁");}}});t1.start();t2.start();}
}



这种方法是解决死锁,最简单最可靠的方法。

相关内容

热门资讯

汽车油箱结构是什么(汽车油箱结... 本篇文章极速百科给大家谈谈汽车油箱结构是什么,以及汽车油箱结构原理图解对应的知识点,希望对各位有所帮...
重大消息战皇大厅开挂是真的吗... 您好:战皇大厅这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...
嵌入式 ADC使用手册完整版 ... 嵌入式 ADC使用手册完整版 (188977万字)💜&#...
盘点十款牵手跑胡子为什么一直... 您好:牵手跑胡子这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游...
senator香烟多少一盒(s... 今天给各位分享senator香烟多少一盒的知识,其中也会对sevebstars香烟进行解释,如果能碰...
终于懂了新荣耀斗牛真的有挂吗... 您好:新荣耀斗牛这款游戏可以开挂,确实是有挂的,需要了解加客服微信8435338】很多玩家在这款游戏...
盘点十款明星麻将到底有没有挂... 您好:明星麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【5848499】很多玩家在这款游戏...
总结文章“新道游棋牌有透视挂吗... 您好:新道游棋牌这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7682267】很多玩家在这款游...
终于懂了手机麻将到底有没有挂... 您好:手机麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...
领奥平衡车质量怎么样(左拉和领... 本篇文章极速百科给大家谈谈领奥平衡车质量怎么样,以及左拉和领奥平衡车哪个好对应的知识点,希望对各位有...