🔒一.什么是死锁?
死锁就是两个或者两个以上的线程在执行过程中,由于资源竞争或者由于彼此通信而造成的阻塞现象,若无外力作用,都将无法推进下去.
二.关于死锁的情况🤷♂️
情况☝️:一个线程 一把锁,如果是可重入锁不会产生死锁,不可重入锁会产生死锁.
我们该怎么去理解这种情况呢???
下面的这个代码可以帮助大家理解这种情况:
public static void main(String[] args) {
Object locker=new Object();
Thread t1=new Thread(()->{
synchronized (locker){
synchronized (locker){
}
}
});
}
这样的情况就容易造成死锁,同一个线程对同一个对象锁;
外层首先获得锁对象,然后进去代码也尝试对这个对象加锁,它要加锁成功就必须等外层锁释放,但是外层释放锁又必须等里面代码执行完.就这样两个锁就僵持在这里,就产生了死锁.
情况✌️:两个线程两把锁,对于这种情况,即使是可重入锁也是会造成死锁的.
首先我们给大家画个图让大家理解一下这是个什么情况
为了避免大家写出这样的代码,我给大家写一个两个线程两把锁产生死锁的代码.
public static void main(String[] args) {
Object locker1=new Object();
Object locker2=new Object();
Thread t1=new Thread(()->{
synchronized (locker1){
System.out.println("线程t1,获取锁1");
}
synchronized ((locker2)){
System.out.println("线程t1,又尝试获取锁2");
}
});
t1.start();
Thread t2=new Thread(()->{
synchronized (locker2){
System.out.println("线程t2,获取锁2");
}
synchronized (locker1){
System.out.println("线程t2,又尝试获取锁1");
}
});
t2.start();
}
这就是典型的在自己没有释放锁的情况又尝试去获取对方的锁.
如果大家对上面的两个例子还不够理解的话,我们举一个生活中的例子来帮助大家来理解.
eg:大家在生活中肯定都吃过饺子,吃饺子肯定是要蘸料的.比如我是一个喜欢吃辣又喜欢吃酸的妹子,我有一个朋友呢,她的口味和我就很像,她也是又吃酸又吃辣的;有一次呢,我们一起去吃饺子,她先拿到了醋,而我就先拿辣椒;事情的重点就来了,我就让她把手中的醋给我,然后她让我把手中的辣椒给她.我们同时还占着自己手中的蘸料不放.这样我们就僵持在这里,谁都释放不了,谁都加不了.这就一种死锁.我和我的朋友就是两个线程,手中的辣椒和醋就是两把锁
情况👌:N个线程M把锁[哲学家问题]
哲学家问题:
假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉.然后哲学家们也不善于交谈,假如他们同时都拿起了左手边的叉子,然后都在等待右手边的叉子,哲学家们都很固执,没吃到面都不会放下手中的叉子.然后这就产生了死锁,都僵持在这里
这里哲学家就是一个个的线程,叉子就相当于锁.
三.死锁的必要条件🔒[同时具备]
- 互斥使用:一个线程拿到一把锁之后,另一个线程不能使用(基本特点)
- 不可抢占:一个线程拿到锁,只能自己主动释放,不能是被其他线程强行占用
- 请求和保持:意思就是尝试获取另外的锁时,自己的锁是不释放的.典型的"吃着碗里的看着锅里的"
- 循环等待:类似于上面的哲学家问题,一号哲学家等着二号手中的释放,二号等三号的…这样进行循环等待.
四.如何解决死锁问题
破坏死锁的任意一个或者多个条件就可以解决死锁问题啦,其中被修改的条件只能是后面的两个:请求与保持和循环等待.因为基本特点是改变不了的.
1.修改请求与保持:当我们线程获得一把锁之后不能再去请求获取另一把锁
2.破坏循环等待:我们可以为锁都编上号,约定都只能先对小编号的加锁,再才能对编号大的加锁
还是上面哲学家问题:
我们可以规定都拿起手边最小编号的筷子:(就得到以下的图)
然后我们再让拿起手边大编号的筷子,这样只有5号滑稽老铁可以拿到编号5的筷子,其他老铁就需要阻塞等待,这样5号滑稽老铁就可以吃上面条了.吃完之后他就可以释放锁了(放下筷子),这样4号滑稽老铁就可以就可以拿起编号4的筷子,它就可以吃上面条了…以此类推,这样所有的滑稽老铁都吃上面条了.
代码可以这样实现:
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker1){
synchronized (locker2){
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized (locker1){
synchronized (locker2){
}
}
});
t2.start();
}
这样就不会产生死锁了,当t1线程对locker1加锁了,t2线程也想对locker1加锁时,
t2线程就需要阻塞等待,等t1线程执行完t2才能够获取锁.
_OK,以上就是我对死锁的理解,如有错误请大家指出!_😊😊😊