观察死锁是如何产生的,观察下面这段代码
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() ->{
//获取lock1
synchronized (lock1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//持有lock1的前提下获取lock2
synchronized (lock2){
System.out.println("t1有两把锁");
}
}
});
Thread t2 = new Thread(() ->{
//获取lock2
synchronized (lock2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//持有lock2的前提下获取lock1
synchronized (lock1){
System.out.println("t2有两把锁");
}
}
});
t1.start();
t2.start();
}
运行一段时间后,许久都没有等到t1或者t2打印的内容,并且通过工具可以观察到,此时t1和t2都处于BLOCKED状态
并且t1的锁持有者为t2,t2的锁持有者为t1,因为这时候t1在等待t2释放lock2,t2在等待t1释放lock1,这时就产生了死锁.
死锁产生的原因(必要条件)
1.互斥使用,获取锁的过程是互斥的。
一个线程拿到这把锁,另一个线程也想拿到这把锁,就要等待
2.不可抢占
一个线程拿到锁之后,其他的线程只能等待该线程主动释放锁,不能直接抢过来
3.请求保持
一个线程拿到锁A后,在持有锁A的前提下去尝试获取锁B
4.环路等待
如A在等待B释放锁2,B在等待C释放锁3,而C在等待A释放锁1,这时等待就形成了一个循环,产生了死锁
如何解决死锁?
想要解决死锁,就要尝试破坏产生死锁的必要条件
对于条件1和条件2,这都是锁的特性。如果把这两个条件破坏掉,那么这个“锁”也不能叫锁了,所以这两个条件无法破坏。观察条件3,这个问题似乎是由代码结构产生的,而一些情况下,如 用户的需求就是这样 那么我们去破坏条件3的优先级也没有那么高。
那只剩条件4了,关于这个条件的解决有很多优秀的案例 如 银行家算法 以及 哲学家吃面问题,这里不再展开赘述。破坏这个条件只需要我们规划加锁顺序,对加锁顺序进行优化后,虽然可能导致执行效率不如预期,但是可以避免死锁问题。