目录
死锁是怎么回事?
有一天我和小明一起火锅,小明点的麻酱味的蘸料,我点的麻辣味的蘸料,我想尝尝小明麻酱味的,小明想尝尝麻辣味的,但是如果我两互不相让,那么就会产生死锁。
死锁的三个典型情况
1.一个线程一把锁,连续加锁两次,如果锁是不可重入锁就会死锁
比如synichronized,ReentrantLock都是可重入锁,他们连续加锁两次不会出现死锁
2.两个线程两把锁,t1和t2各自先针对锁A和锁B加锁,再尝试获取对方的锁,就好比吃火锅,就会产生相互阻塞,两个线程都无法获取到对方的锁
public class ThreadDemo {
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(()->{
synchronized (locker1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker2){
System.out.println("t1获取到locker2");
}
}
});
Thread t2 = new Thread(()->{
//t2获取到locker2
synchronized (locker2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker1){
System.out.println("t2获取到locker1");
}
}
});
t1.start();
t2.start();
}
}
3.多个线程多把锁:哲学家就餐问题:
有5位哲学家围坐在一张圆桌旁,桌子中央放了一碗面,,每两个人之间放了一把叉子,每位哲学家思考人生饥饿了这就相当于线程的阻塞状态,然后开始吃面,这相当于线程获取到锁,哲学家必须获得两把叉子,且每个人只能直接从紧邻自己的左边或右边去取叉子,但是由于操作系统随机调度,这五位哲学家可能会随时饥饿了吃面条,也可能随时要思考人生
假设同一时间 五个哲学家同时要开始吃面,那么他们会同时拿起左叉子,所有的哲学家都拿不起右手的筷子,都在等待右边的哲学家把筷子放下,但是互不忍让,这样所有的人都拿不了有筷子,也就所有的线程都互相阻塞。
可重入和不可重入
一个线程针对一把锁,连续加锁两次,是否会死锁,不会死锁就是可重入的,synchronized就是可重入锁
死锁的四个必要条件
1.互斥使用:线程1拿到锁,线程2就得等着
2.不可抢占:线程1拿到锁之后,必须是线程1主动释放锁,不能说是线程2就把锁给强行获取到
3.请求和保持 :线程1拿到锁A之后,再尝试获取锁B,A这把锁还会保持,不会因为获取锁B就把A释放了
4.循环等待:线程1尝试获取锁A和锁B,线程2尝试获取锁B和锁A,线程1在获取B的时候等待线程2释放B,同时线程2在获取A的时候等待线程1释放A
如何破除死锁
在死锁的四个必要条件中来看,前三个条件是synchronized锁的基本特性,那么我们只需要打破必要条件即可,就是第四个条件循环等待,就好比打仗的时候,要找到敌军的突破口去打更容易赢
办法:给锁编号,然后指定一个固定的顺序,比如从小到大来加锁,任意线程加多把锁的时候,都让线程按照顺序来加锁,那么循环等待自然破除。比如哲学家就餐问题
我们给叉子(锁)编号 规定哲学家先拿自己左右手两边编号小的,这样的话会发现最上面的哲学家左右手两边最小的是1,但是1已经被别人拿走了,那么这时候他就会阻塞等待,这时候他左边的哲学家就可以拿起两把叉子去吃面了,他吃完以后就放下④号和⑤号筷子,这时候他左边的哲学家就可以开始拿着③号和④号筷子吃饭,就这么下去,所有的哲学家都会吃到面。而吃火锅蘸料问题,我们也可以给蘸料编号,我和小明都先吃编号小的,再吃编号大的蘸料,这样问题就迎刃而解了。
public class ThreadDemo {
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(()->{
synchronized (locker2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker1){
System.out.println("t1获取到locker2");
}
}
});
Thread t2 = new Thread(()->{
//t2获取到locker2
synchronized (locker2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker1){
System.out.println("t2获取到locker1");
}
}
});
t1.start();
t2.start();
}
}
这样t1和t2都可以获取到对方的锁,就不会产生死锁了