一、死锁的定义
死锁是指,有两个或两个以上的线程在执行的过程中,由于竞争的资源或者彼此通信而造成的一种阻塞状态,若无外力作用,他们将都无法进行下去,从而形成一直阻塞的状态叫死锁。
简单来说就是小李拿了东西A,小张拿了东西B,小李现在还想要东西B,小张还想要东西A,但是两个人都不放弃原来的,还要对方的,一直僵持的状态就叫死锁。
画一个图来解释一下:
二、产生死锁的必要条件
- 互斥条件
一个资源只能被一个线程所拥有的,若一个线程已经拥有了该资源,那么其他想获取该资源的线程就需要阻塞等待。 - 不可剥夺条件
当一个资源被线程获取了之后,如果该线程不主动释放该资源,那么该资源一直被占有,其他想获取该资源的线程就要一直进行等待。 - 请求并持有条件
一个线程已经拥有了一个资源,还要请求新的资源。 - 循环等待条件
产生死锁一定是发生了环路等待,形成线程资源环形链。
以上是产生死锁的四个必要条件,缺一不可,产生死锁的时候这四个条件一定是都满足的,那么就表示,要想避免死锁,破坏其中一个条件即可。
三、自己实现一个死锁
public class DeadLockCSDN {
public static void main(String[] args) {
//资源A和B
Object A = new Object();
Object B = new Object();
//第一个线程
Thread t1 = new Thread(() -> {
//先得到资源A
synchronized (A){
System.out.println("线程1已经获得资源A");
try {
//这里线程休眠两秒是为了保证线程1拿到A
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//拿到A之后再去获取B资源
synchronized (B){
System.out.println("线程1已经获得资源B");
}
}
});
//第二个线程
Thread t2 = new Thread(() -> {
//先获得资源B
synchronized (B){
System.out.println("线程2已经获得资源B");
try {
//保证线程2 获得资源B
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再去请求获得A资源
synchronized (A){
System.out.println("线程2已经获得资源A");
}
}
});
t1.start();
t2.start();
}
}
运行结果:
我们可以看到线程1和线程2都分别获得了A资源和B资源,但是都没有得到自己第二个想获得的资源,我们的程序也一直没有退出。没有变化,就是因为形成了死锁,大家僵持在了一起。
四、避免死锁
我们知道形成死锁的四个条件分别是:互斥条件、不可剥夺条件、请求并持有条件、循环等待条件,那么我们看这四个条件,互斥条件和不可剥夺条件是我们没有办法改变的,请求并持有条件可以改变但是我们就是想实现,每个线程都能获得两个资源的情况,所以我们只能去打破循环等待条件了,那么打破循环等待条件就是让两个线程之间获取资源的顺序不要穿插在一起,可以让一个线程先获得两个资源,使用完毕后释放,另一个线程再获取这两个资源,就可以保证两个线程都能正常执行。
画个图解释一下:
用代码实现一下这个过程:
public class DeadLockCSDN {
public static void main(String[] args) {
//资源A和B
Object A = new Object();
Object B = new Object();
//第一个线程
Thread t1 = new Thread(() -> {
//先得到资源A
synchronized (A){
System.out.println("线程1已经获得资源A");
try {
//这里线程休眠两秒是为了保证线程1拿到A
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//拿到A之后再去获取B资源
synchronized (B){
System.out.println("线程1已经获得资源B");
}
}
});
//第二个线程
Thread t2 = new Thread(() -> {
//先获得资源A
synchronized (A){
System.out.println("线程2已经获得资源A");
try {
//保证线程2 获得资源B
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再去请求获得B资源
synchronized (B){
System.out.println("线程2已经获得资源B");
}
}
});
t1.start();
t2.start();
}
}
运行结果:
程序正确执行并退出!我们将线程2获得资源的顺序变成和线程1一样的这样就不会出现资源循环等待了,打破了循环等待条件,也就避免了死锁,程序就可以正常执行,线程1和线程2都获得了原本想获得的两个资源。