死锁的成因
关于死锁的情况有三种
1.一个线程,一把锁
这种情况是当这个线程使用锁后没有进行解锁,然后再次使用锁的时候就会出现死锁的情况.
当然,这个只能针对不可重入锁,对于可重入锁不造成影响.
2.两个线程两把锁
出现以下代码时就可能造成死锁
public class Main{
public static Object o1 = new Object();
public static Object o2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (o1){
synchronized (o2){
System.out.println("t1");
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized (o2){
synchronized (o1){
System.out.println("t2");
}
}
});
t2.start();
}
}
当t1和t2同时进行第一把加锁就会出现死锁的情况
3.N个线程,M把锁
线程数量和锁数量更多了,就更容易死锁了
著名的哲学家的就餐问题
这五个哲学家随机的拿起筷子吃饭和放下筷子
如果他想拿起筷子,被别人占用了,就会就行等待,等的过程中不会放下已经拿起的筷子;
假设这五个哲学家同时拿起左手边的筷子就死锁了
但在线程中更加复杂,更容易出现死锁
死锁的四个必要条件
1.互斥使用,一个线程拿到一把锁之后,另一个线程不能使用.(锁的基本特点)
2.不可抢占,一个线程拿到锁,只能自己主动释放,不能是被其他线程强行占有(锁的基本特点)
3.请求和保持,"吃着碗里的看着锅里的", 在拿到一号锁之后,再想拿二号锁,并且不会放掉一号锁(代码的特点)
4.循环等待,形成一个进程等待环路,环路中的每个进程所占有的资源都在被别的进程所申请(代码的特点)
四个条件缺一不可
如何避免死锁
一个简单有效的办法,就是破解循环等待这个条件.
针对锁进行编号,如果需要同时获取多把锁,约定加锁顺序,务必是先对小的编号进行加锁,后对大的编号加锁
比如在哲学家就餐问题当中:
约定每个哲学家都只能先去拿一号筷子再去拿二号筷子,一号筷子如果被拿走了则等待
在我们写代码的时候注意加锁顺序就行了.
我们约定先对小号锁进行加锁,在加大号锁,然后所有线程都遵守这个顺序即可.
public class Main{
public static Object o1 = new Object();
public static Object o2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (o1){
synchronized (o2){
System.out.println("t1");
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized (o1){
synchronized (o2){
System.out.println("t2");
}
}
});
t2.start();
}
}