1.死锁的定义
死锁:指的是两个或两个以上的进程在执⾏过程中,由于竞争资源或者由于彼此通信⽽造成的⼀种阻塞的现象,若⽆外⼒作⽤,它们都将⽆法推进下去。
简而言之,就是两个进程在各自拥有锁的前提下,又尝试获取对方锁,从而导致程序一直处于阻塞状态的情况。
(1)图示说明:

线程1在拥有资源1的情况下,尝试申请线程2所占有的资源2,而线程2又在拥有资源2的情况下,尝试获取线程1释放的资源1,但是两个线程此时都占有资源且不释放,所以它们一直处于互相等待的状态,即死锁状态。
【注】线程和锁之间的关系:多对多的关系。一个线程可以拥有多把锁,而一把锁只能被一个线程所占有。
(2)代码实例:
用synchronized对锁对象进行加锁,创建两个线程,在线程1中占有锁A尝试获取锁B,在线程2中占有锁B尝试获取锁A,看看会产生怎样的结果?
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(()->{
synchronized (lockA){
System.out.println("线程1:获得锁A");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println("线程1:获得锁B");
}
}
});
t1.start();
Thread t2 = new Thread(()->{
synchronized (lockB){
System.out.println("线程2:获得锁B");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA){
System.out.println("线程2:获得锁A");
}
}
});
t2.start();
}
代码的执行的结果如下:

结果明显可以看出,程序发生了死锁,线程1和线程2都尝试获取对方的锁但是没有获取到,程序执行了很长时间并未结束,两个线程在互相等待对方释放资源。
(3)查看死锁
①jconsole.exe
打开Java文件目录,找到/jdk/bin/jconsole.exe并打开,点击本地进程连接。

在线程栏目下方点击检测死锁按钮,查看产生死锁的进程。

在死锁框内可以查看所有的死锁线程,右边的详情信息中也可以清楚地看死锁到线程的状态:BLOCKED(阻塞状态)。

②jvisualvm.exe
在Java/jdk/bin目录下,找到jvisualvm.exe并打开,双击本地进程。

在线程选项下,有明显的提示,检测到死锁。关于死锁的具体详情点击线程Dump进行查看。


③jmc.exe
相比于前两种方式,jmc.exe加载的信息会更加详细,所以加载速度比较慢。
在Java/jdk/bin目录下,找到jmc.exe并打开,点击JMX控制台。

进入控制台,选择本地进程,启动JMX控制台。

在JMX控制台内,点击线程,选择死锁检测选项,即可看到产生死锁的两个线程,以及它们的状态等信息。

2.死锁的产生原因
(1)互斥条件:⼀个资源只能被⼀个线程占有,当这个资源被占⽤之后其他线程就只能等待。
(2)不可剥夺条件:当⼀个线程不主动释放资源时,此资源⼀直被拥有线程占有。
(3)请求并持有条件:线程已经拥有了⼀个资源之后,有尝试请求新的资源。
(4)环路等待条件:产⽣死锁⼀定是发⽣了线程资源环形链。
【注】以上四个条件是产生死锁的必要条件,若要产生死锁,以上四个条件缺一不可。也就是说死锁的产生不是由于上述四个条件当中的某一个因素所导致的,而是四个因素共同作用所导致的。
3.解决死锁
解决思路:“反其道而行之”,若要解决思索问题,需打破形成死锁的一个或多个条件即可。
【分析】
1.互斥条件:由于系统存在很多独占资源,破坏 " 互斥使用“ 这一必要条件不太现实,是不能被认为打破的。
2. “不可剥夺“ 条件:当一个线程拥有资源后,无法强迫它进行释放,这是系统所设定的本质特征,所以也无法进行改变。
3. “请求并持有“ 条件:不是系统特征,可以打破,进行认为控制。要求每个进程在运行前必须一次性申请它所要求的所有资源,且仅当该进程所要资源均可满足时才给予一次性分配。
代码实例:
public class unDeadLock {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()->{
synchronized (A){
System.out.println("线程1:得到锁A");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:释放锁A");
}
},"线程1");
t1.start();
Thread t2 = new Thread(()->{
synchronized (B){
System.out.println("线程2:得到锁B");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:释放锁B");
}
},"线程2");
t2.start();
}
}
运行结果:

4. 环路等待条件:采用资源有序分配法,把系统中所有资源编号,进程在申请资源时必须严格按资源编号的递增次序进行,否则操作系统不予分配。通俗的说,就是改变进行的执行顺序,使之不会产生死锁。

改变原有的执行顺序:
1.线程1得到锁A,线程2进入阻塞状态。
2.线程1在没有释放锁A的请求下,又得到了锁B,线程2还是在阻塞状态。
3.线程1执行完了业务释放了锁B,线程2仍处于阻塞状态。
4.线程1又释放了锁A,执行结束。
5.线程2得到锁A,继续执行。
6.线程2又得到了锁B。
7.线程2释放锁B。
8.线程2释放锁A,执行结束。
代码示例:
public class unDeadLock{
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()->{
synchronized (A){
System.out.println("线程1:得到锁A");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println("线程1:得到锁B");
System.out.println("线程1:释放锁B");
}
System.out.println("线程1:释放锁A");
}
},"线程1");
t1.start();
Thread t2 = new Thread(()->{
synchronized (A){
System.out.println("线程2:得到锁A");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println("线程1:得到锁B");
System.out.println("线程1:释放锁B");
}
System.out.println("线程1:释放锁A");
}
},"线程2");
t2.start();
}
}
运行结果:
