一、死锁概述
死锁指两个或两个以上线程互相占有对方所需锁资源,由于锁的不可抢占性,使这些线程都陷入停滞的状态。例如线程A的持有锁a,线程B持有锁b,在都未解锁的情况下线程A想要对b对象加锁,线程B想要对a对象进行加锁,由于锁除非加锁者主动解锁否则无法被抢占这一基本特性,线程A和线程B都进入等待状态,等候对方解锁,然而不继续执行双方都不会解锁,由此形成逻辑闭环。
public class ThreadDemo1 {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
Thread A = new Thread(() -> {
synchronized(a) {
//等待确保B线程获取到锁b
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(b) {
System.out.println("t1拿到两把锁");
}
}
});
Thread B = new Thread(() -> {
synchronized(b) {
//等待确保A线程获取到锁a
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(a) {
System.out.println("t2拿到两把锁");
}
}
});
A.start();
B.start();
}
}
运行结果如下,死锁产生,进程停止
二、死锁产生的原因
死锁产生需要具备四个条件,缺一不可
1.互斥使用
当一个线程拿到了这把锁,另一个线程也想获取,就只能阻塞等待
2.不可抢占
一个线程拿到锁之后只能主动解锁,不会让别的线程将锁抢占
3.请求保持
一个线程占有锁资源,同时又申请其他已被占有的资源,陷入阻塞
4.循环等待
等待线程间形成逻辑闭环,永久阻塞
三、避免死锁
1、加锁顺序:
当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。当然这种方式需要你事先知道所有可能会用到的锁,然而总有些时候是无法预知的。
public class ThreadDemo1 {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
Thread A = new Thread(() -> {
synchronized(a) {
//调整加锁顺序,使两线程都首先获取到a锁才能获取b锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(b) {
System.out.println("t1拿到两把锁");
}
}
});
Thread B = new Thread(() -> {
synchronized(a) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(b) {
System.out.println("t2拿到两把锁");
}
}
});
A.start();
B.start();
}
}
2、加锁时限:
加上一个超时时间,若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。但是如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。
3、死锁检测:
死锁检测即每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。
————————————————
(版权声明:本段来自CSDN博主「孔子-说」的原创文章)
待补充……