共计 3281 个字符,预计需要花费 9 分钟才能阅读完成。
起源:blog.csdn.net/xiewenfeng520/article/details/107230996
前言
只对死锁代码感兴趣的能够间接跳到第三大节 必然死锁示例,如果对死锁还不太理解的,咱们能够一起来探讨以下几个议题
- 什么是死锁?
- 死锁有什么危害和特点?
- 代码实现一个必然死锁的示例
- 剖析死锁的过程
1. 什么是死锁?
关键词:并发场景,多线程
首先咱们须要晓得,死锁肯定产生在并发场景中。咱们为了保障线程平安,有时会给程序应用各种能保障并发平安的工具,尤其是锁,然而如果在应用过程中解决不得当,就有可能会导致产生死锁的状况。
关键词:互不相让
死锁是一种状态,当两个(或多个)线程(或过程)互相持有对方所须要的资源,却又都不被动开释本人手中所持有的资源,导致大家都获取不到本人想要的资源,所有相干的线程(或过程)都无奈持续往下执行,在未扭转这种状态之前都不能向前推动,咱们就把这种状态称为死锁状态,认为它们产生了死锁。
简而言之,死锁就是两个或多个线程(或过程)被无限期地阻塞,互相期待对方手中资源的一种状态。
两个线程死锁的状况
如图所示,线程 1 曾经持有了 锁 1,同时 线程 2 也曾经持有了锁 2,而后 线程 1 尝试获取 锁 2,然而 线程 2 并没有开释 锁 2,所以 线程 1 处于阻塞状态,同理可知,图中的 线程 2 获取 锁 1 也会被阻塞。
这样一来,线程 1 和 线程 2 就产生了死锁,因为它们都互相持有对方想要的资源,却又不开释本人手中的资源,造成互相期待,而且会始终期待上来。
2. 死锁的影响和危害
2.1 死锁的影响
死锁的影响在不同零碎中是不一样的,影响的大小一部分取决于以后这个零碎或者环境对死锁的解决能力。
2.1.1 数据库中
例如,在数据库系统软件的设计中,思考了监测死锁以及从死锁中复原的状况。在执行一个事务的时候可能须要获取多把锁,并始终持有这些锁直到事务实现。在某个事务中持有的锁可能在其余事务中也须要,因而在两个事务之间有可能产生死锁的状况,一旦产生了死锁,如果没有内部干预,那么两个事务就会永远的期待上来。
但数据库系统不会放任这种状况产生,当数据库检测到这一组事务产生了死锁时,依据策略的不同,可能会抉择放弃某一个事务,被放弃的事务就会开释掉它所持有的锁,从而使其余的事务持续顺利进行。
此时程序能够从新执行被强行终止的事务,而这个事务当初就能够顺利执行了,因为所有跟它竞争资源的事务都曾经在方才执行结束,并且开释资源了。
2.1.2 JVM 中
在 JVM 中,对于死锁的解决能力就不如数据库那么弱小了。如果在 JVM 中产生了死锁,JVM 并不会主动进行解决,所以一旦死锁产生,就会陷入无穷的期待。
2.2 死锁的危害以及特点
关键词:概率性事件
死锁的问题和其余的并发平安问题一样,是概率性的,也就是说,即便存在产生死锁的可能性,也并不是 100% 会产生的。如果每个锁的持有工夫很短,那么发生冲突的概率就很低,所以死锁产生的概率也很低。然而在线上零碎里,可能每天有几千万次的“获取锁”、“开释锁”操作,在巨量的次数背后,整个零碎产生问题的几率就会被放大,只有有某几次操作是有危险的,就可能会导致死锁的产生。
也正是因为死锁“不肯定会产生”的特点,导致提前找出死锁成为了一个难题。压力测试尽管能够检测出一部分可能产生死锁的状况,然而并不足以齐全模仿实在、长期运行的场景,因而没有方法把所有潜在可能产生死锁的代码都找进去。
关键词:危害大,产生几率不高
一旦产生了死锁,依据产生死锁的线程的职责不同,就可能会造成 子系统解体、性能升高 甚至 整个零碎解体 等各种不良后果。而且死锁往往产生在高并发、高负载的状况下,因为可能会间接影响到很多用户,造成一系列的问题。以上就是死锁产生几率不高然而危害大的特点。
3. 必然死锁示例
public class MustDeadLockDemo {public static void main(String[] args) {Object lock1 = new Object();
Object lock2 = new Object();
new Thread(new DeadLockTask(lock1, lock2, true), "线程 1").start();
new Thread(new DeadLockTask(lock1, lock2, false), "线程 2").start();}
static class DeadLockTask implements Runnable {
private boolean flag;
private Object lock1;
private Object lock2;
public DeadLockTask(Object lock1, Object lock2, boolean flag) {
this.lock1 = lock1;
this.lock2 = lock2;
this.flag = flag;
}
@Override
public void run() {if (flag) {synchronized (lock1) {System.out.println(Thread.currentThread().getName() + "-> 拿到锁 1");
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-> 期待锁 2 开释...");
synchronized (lock2) {System.out.println(Thread.currentThread().getName() + "-> 拿到锁 2");
}
}
}
if (!flag) {synchronized (lock2) {System.out.println(Thread.currentThread().getName() + "-> 拿到锁 2");
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-> 期待锁 1 开释...");
synchronized (lock1) {System.out.println(Thread.currentThread().getName() + "-> 拿到锁 1");
}
}
}
}
}
}
执行后果:
能够看到程序始终处于阻塞状态。
4. 过程剖析
其实下面的代码示例产生死锁的过程就是第一大节中 两个线程产生死锁 的状况,这里咱们把图拿过去,不便剖析。
本文应用 IDEA 进行调试,将断点打在 33 行,run 办法的第一行,抉择 Thread 模式。
留神:调试过程,因为有人为的等待时间,所以并不会产生死锁,这里只是演示线程执行的程序和状态。
第一步,线程 1 进入,flag = true,进入第一个 synchronized 同步块,拿到 lock1(锁 1)
第二步,间接点击 Resume Program(F9),进入线程 2,此时 flag = false,进入第二个 synchronized 同步块
当然如果 Thread.sleep 的工夫够长,或者操作速度够快的话,也能产生死锁。
5. 总结
本章咱们探讨了什么是死锁,以及死锁的影响和危害,演示了一个必然死锁的例子,而后应用 IDEA 工具调试了两个线程产生死锁的步骤。
在 JVM 中如果产生死锁,可能会导致程序局部甚至全副无奈持续向下执行的状况,所以死锁在 JVM 中所带来的危害和影响是比拟大的,咱们须要尽量避免。
最初如果在面试中碰到这一题,心愿大家都能顺利通过。
参考:《Java 并发编程 78 讲》- 徐隆曦
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿 (2021 最新版)
2. 别在再满屏的 if/ else 了,试试策略模式,真香!!
3. 卧槽!Java 中的 xx ≠ null 是什么新语法?
4.Spring Boot 2.5 重磅公布,光明模式太炸了!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!