前言:
此时我们对“简述死锁的定义”大约比较讲究,大家都想要知道一些“简述死锁的定义”的相关知识。那么小编也在网络上收集了一些有关“简述死锁的定义””的相关资讯,希望小伙伴们能喜欢,咱们一起来学习一下吧!在Java面试过程中,死锁也是高频考察点之一,如果线上环境出现了死锁问题,估计要拿出一位程序员去祭天才行了。
在进行多线程编程中,我们为了防止多个线程同时对一个共享资源进行操作,一般都会在操作这个共享资源之前加上互斥锁,只有成功获取到锁的线程,才能够操作这个共享资源。但是在一些特定条件下,也会产生一些问题,比如死锁,接下来我们就聊聊死锁的问题。
1. 死锁的定义
在 Java 中,死锁(Deadlock)情况是指:两个或两个以上的线程持有不同系统资源的锁,线程彼此都等待获取对方的锁来完成自己的任务,但是没有让出自己持有的锁,线程就会无休止等待下去。线程竞争的资源可以是:锁、网络连接、通知事件,磁盘、带宽,以及一切可以被称作“资源”的东西
概念性的东西读起来可能有点抽象,那我举个比较容易理解的例子:假设有两个线程A 和 B,两把锁 LockA和LockB,线程A持有LockA锁,线程B持有LockB锁 , 在双方不释放锁的情况下,再尝试去获取对方持有的锁,也就是说,线程A 手里拿着LockA锁不放,又去获取LockB锁(此时LockB锁在线程B手里),而线程B手里拿着LockB锁不放,又去获取LockA锁(此时LockA锁在线程A手里),在这种情况下,就会陷入无限等待的死锁状态。
代码示例:
public class DeadLockDemo {
//两把锁 LockA 和 LockB
private static Object lockA = new Object();
private static Object lockB = new Object();
public static void main(String[] args) {
//开启两个线程 A B
//线程A
new Thread(()->{
//线程A获取到LockA锁
synchronized (lockA){
System.out.println("线程A获取到LockA锁");
try {
//睡眠100毫秒,确保B线程获取到LockB锁
Thread.sleep(100);
//尝试获取lockB,此时lockB在线程B手里没有释放
synchronized (lockB){
System.out.println("线程A获取到LockB锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
//线程B
new Thread(()->{
//线程B获取到LockB锁
synchronized (lockB){
System.out.println("线程B获取到LockB锁");
try {
//睡眠100毫秒,确保A线程获取到LockA锁
Thread.sleep(100);
//尝试获取lockA,此时lockA在线程A手里没有释放
synchronized (lockA){
System.out.println("线程B获取到LockA锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
输出:
线程A获取到LockA锁
线程B获取到LockB锁
2.如何检测和排查死锁
我们知道了什么是死锁,下一步就是当遇到死锁问题时,如何利用工具去排查和检测死锁问题,下面我们就介绍两款好用的工具来帮助我们检测死锁。
2.1 使用Jstack 工具
JDK中自带的 jstack 工具是一个线程堆栈分析工具,可以帮助我们排查Java程序中是否有死锁问题。
首先我们在命令行输入jps查看当前正在执行任务的PID
atguigu@Zhang JavaSE % jps
# 以下显示的是进程名称和对应的PID号
44080 Launcher
44432 DeadLockDemo
29560
44094 Jps
可以看到任务进程的 PID 为 44432,然后我们使用 jstack工具查看当前进程的堆栈信息
atguigu@Zhang JavaSE % jstack 44432
# 以下显示的是进程的堆栈信息
Found one Java-level deadlock:
=============================
"B":
waiting to lock monitor 0x000000014980eec0 (object 0x000000076ac71460, a java.lang.Object),
which is held by "A"
"A":
waiting to lock monitor 0x0000000149810360 (object 0x000000076ac71470, a java.lang.Object),
which is held by "B"
Java stack information for the threads listed above:
===================================================
"B":
at com.atguigu.thread.DeadLockDemo.lambda$main$1(DeadLockDemo.java:42)
- waiting to lock <0x000000076ac71460> (a java.lang.Object)
- locked <0x000000076ac71470> (a java.lang.Object)
at com.atguigu.thread.DeadLockDemo$$Lambda$2/1791741888.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"A":
at com.atguigu.thread.DeadLockDemo.lambda$main$0(DeadLockDemo.java:23)
- waiting to lock <0x000000076ac71470> (a java.lang.Object)
- locked <0x000000076ac71460> (a java.lang.Object)
at com.atguigu.thread.DeadLockDemo$$Lambda$1/1329552164.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
2.2 使用Jconsole 工具
Jconsole是JDK自带的监控工具。它用于连接正在运行的本地或者远程的JVM,对正在运行的Java应用程序的资源消耗和性能进行监控,提供强大的可视化界面,本身占用的服务器内存很小,甚至可以说几乎不消耗。
# 命令行输入 jconsole 指令
jconsole
弹出图形化界面
可以看到以下结果
可以看到进程中是存在死锁的 。
3.如何避免产生死锁
其实产生死锁也不是那么容易的,需要满足四个条件,才会产生死锁。
互斥,也就是多个线程在同一时间使用的不是同一个资源。持有并等待,持有当前的锁,并等待获取另一把锁不可剥夺,当前持有的锁不会被释放环路等待,就是两个线程互相尝试获取对方持有的锁,并且当前自己持有的锁不会释放。
我们只需要让其中一个条件不成立,那么就可以避免死锁问题的产生。一般最常见的解决方式就是使用资源有序分配法,来使环路等待条件不成立。
环路等待就是我们刚开始代码演示的那种情况,两个线程互相尝试获取对方的锁,但是他们两个都不会释放自己的锁,这样就会陷入一个无限循环等待,这种情况就是环路等待。
资源有序分配法其实很简单,就是把线程获取资源的顺序调整为一致的即可,资源可以理解为代码示例中的锁。
那么我们只需要修改线程A或者线程B的代码即可。线程B原本是先获取LockB再获取LockA,改成和线程A一样的获取顺序,先获取LockA,再获取LockB:
//线程B 获取锁的顺序和线程A保持一致
new Thread(()->{
//线程B第一次也获取LockA 锁
synchronized (lockA){
System.out.println("线程B获取到LockA锁");
try {
//睡眠100毫秒,确保A线程获取到LockA锁
Thread.sleep(100);
//第二次也获取LockB锁
synchronized (lockB){
System.out.println("线程B获取到LockB锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
输出结果:
线程A获取到LockA锁
线程A获取到LockB锁
线程B获取到LockA锁
线程B获取到LockB锁
总结
简单来说,死锁就是多个线程并行执行的时候,抢夺资源而互相等待造成的。只有满足四个条件的时候才会发生。避免死锁问题只需要破坏其中一个条件即可。
标签: #简述死锁的定义