#详细介绍!!! 造成死锁的原因以及解决方案!
本篇主要是介绍什么是死锁,已经死锁产生的原因,如果避免死锁。根据上述的几个问题让我们来阅读本篇文章。
目录
1. 什么是死锁
2. 形成死锁的原因(四个必要条件)
3. 如果有效避免死锁
1. 什么是死锁
死锁主要是锁彼此间进行锁等待,导致每个锁都不能正常执行的情况
例子1:多个锁相互等待造成死锁
假设有两个锁对象为lock1,lock2
线程t1对lock1进行加锁操作,在lock1加锁操作中多lock2再进行加锁操作
线程t2先对lock2进行加锁操作,在lock2加锁操作中又对lock1进行加锁
此时就会造成死锁
伪代码:
public class Demo3 {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
while(true){
synchronized (lock1){
synchronized (lock2){
System.out.println("hello t1");
}
}
}
});
Thread t2 = new Thread(() -> {
while(true){
synchronized (lock2){
synchronized (lock1){
System.out.println("hello t2");
}
}
}
});
t1.start();
t2.start();
}
}
上述代码则必然会造成死锁
t1 和 t2 线程同时执行
当t1线程对lock1加锁成功了之后,此时lock1已经被占用了,然后t2线程对lock2加锁成功了,此时lock2已经被占用了;
在这种情况下,t1线程在去尝试对lock2进行加锁则会进行锁等待,因为lock2进行被线程t2占用了,那么此时t2线程去尝试对lock1进行加锁也是同理,此时t1占用个lock1不放,t2占用着lock2不放,导致对方都拿不到锁进行等待,那么此时t1和t2线程就不能向下执行死锁了
例子2:同一个线程对同一个锁进行加锁并且该锁是不可重入锁,自己给自己进行锁等待,必然死锁
假设有一个锁lock
代码:
public class Demo4 {
public static void main(String[] args) {
Object lock = new Object();
int a = 0;
int b = 0;
synchronized (lock){
a++;
synchronized (lock){
b++;
}
}
}
}
如果是不可重入锁的话上面代码必然死锁
原因,第一次对lock加锁,是此时lock已经被自己占用了,第二次再对lock加锁则发现lock已经被占用此时进行等待lock释放,但是线程已经锁等待了,此时肯定是等不到lock释放的,则造成死锁
注意:由于synchronized是可重入锁,所以上述代码并不会死锁,上面代码只是举个例子说明这种情况
2. 形成死锁的原因(四个必要条件)
本质原因就是,锁之间进行相互等待,等待不到也不会释放手中的资源,相互进行死等导致死锁
而造成这样的底层原因为如下几点:
1. 互斥使用:锁的特性是每次只能有一个线程对锁进行使用,当锁没解锁时,锁资源不会被其他线程正常获取到
2. 不可抢占:资源请求者不能从资源占用者那里抢占到资源,资源只能被占用者主动释放后,请求者才能去获取资源
3. 请求和保持:锁资源请求者在请求其他锁资源时,会保持手中锁资源的占用
如上面例1,t1占用了lock1,再去请求lock2时会仍然占用这lock1,导致其他请求者获取不到
4. 循环等待:线程之间循环占用着彼此的资源,导致彼此间都获取不到资源去正常运行导致死锁
如上面例1:t1占用这个lock1,且正在亲请求lock2,t2占用这lock2,且正在请求lock1;
此时t1占用着t2请求的资源,t2占用着t1请求的资源,这样形成了一个请求闭环,导致相互拿不到资源进而死锁
注意:只要形成了死锁,则必然满足上面四个条件
3. 如果有效避免死锁
既然需要避免死锁,那么就需要根据造成死锁的原因入手。根据上面四条必要条件
1,2点是锁为了保证线程安全锁持有的的特性改变不了
但是3,4则是编码者自己写出来的可以避免
只要破坏了3,4点其中一条自然也就不会死锁
死锁与前面的四个原因是必要关系
方法:破坏循环等待的环路
我们只要保证加锁的顺序一致,那么环路就会得到有效的破坏
前面的例1进行改写:
把两个线程的加锁顺序改为一致
public class Demo3 {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
while(true){
synchronized (lock1){
synchronized (lock2){
System.out.println("hello t1");
}
}
}
});
Thread t2 = new Thread(() -> {
while(true){
synchronized (lock1){
synchronized (lock2){
System.out.println("hello t2");
}
}
}
});
t1.start();
t2.start();
}
}
此时 t1 和 t2 线程都是先对lock1进行加锁,再对lock2进行加锁,此时就不会产生资源彼此占用的环路了也就不会进行死锁等待
解析:
t1先占用了lock1锁,此时t2就获取不到lock1锁了,那么此时t2就在当前代码位置进行锁等待了,更加执行不到去尝试占用lock2锁的代码了,那么t1线程就能正常拿到lock2的锁资源了,所以此时t1和t2只会执行一个线程,不会形成请求资源的闭环。
本篇文章介绍到这里就差不多了,也欢迎各位铁铁指正文章错误的地方