目录:
🌸1.什么是死锁
🌸 2.死锁的情况
🌸3.死锁的条件
🌸4.死锁的解决办法
1.什么是死锁呢?
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线 程被无限期地阻塞,因此程序不可能正常终止.用更加通俗的说法来说,就是锁解不开了.
2.死锁的情况
1.不可重入锁的死锁
2.两个线程两把锁的死锁情况
3.多个线程多把锁的死锁情况(哲学家吃饭问题)
下面来具体分析
1.不可重入锁的死锁
一个线程一把锁,同时对该对象卡卡加锁两次,产生死锁,这就叫做不可重入锁,产生死锁情况
举个很通俗的例子:
小明从外地旅游回来,想要开门,结果才意识到家门钥匙放到了车里,想打开车门取家门钥匙,可是不幸的是车钥匙放到家里了,这个场景就是死锁,没有人可以解开锁
写个伪代码
1synchronized(locker){ 2synchronized(locker){ } }
这就是一个一个线程,同时对同一个对象locker加锁,产生死锁,为了方便讲解,我们讲它俩进行编号,现在对1号加锁,我想对2号加锁,那么就不能,因为:要想对2号加锁,得先对1号解锁,要想对1号解锁,要先把2号的锁给解了,这段逻辑是有矛盾的,所以就造成了死锁的情况
2.两个线程两把锁的死锁情况
举个通俗的例子
大妹和二妹要吃火锅,一盘毛肚在大妹边上,一盘小酥肉在二妹边上,大妹拿起毛肚,二妹拿起小酥肉,大妹说:我想吃小酥肉,你把小酥肉给我我就给你毛肚
二妹说:我想吃毛肚,你把毛肚给我我就给你小酥肉
现在的局面就是僵在这儿了,谁也不让谁
这就造成了死锁的局面
写成伪代码就是
A synchronized(locker1){ synchronized(locker2){ } } B synchronized(locker2){ synchronized(locker1){ } }
上面就是两个线程两把锁,我对A的1加锁,对B的2加锁,此时在A中想对2加锁,在B中想对1加锁,这是做不到的,因此,也就造成了死锁
3.多个线程多把锁(哲学家吃饭问题)
是这样的场景:
五位哲学家围坐在一张桌子上吃面条,但是只有五根筷子,五位科学家同时拿起筷子,要吃面条,当同时拿起两根筷子开始吃,吃完放下筷子,在不吃的时候就思考问题,哲学家都很固执,如果他想拿筷子,但是被别人拿走了就会等待,也不愿意放下自己已经拿到的筷子.
现在每个人同时拿起左手边的筷子,因为固执,所以谁都不愿意放下自己的筷子,那么现在就造成了死锁,就僵在这里了
3.死锁的必要条件
说了这么多,现在来总结一下死锁的必要条件:
1.互斥使用.一个线程拿到一把锁之后,另一个线程不能再拿到了,这个也是锁的基本特点
2.不可抢占.一个线程拿到锁,其他的线程是获取不到锁的,只能等这个锁被主动释放,不能被其他线程强行占有
3.请求和保持.类似于上面的哲学家问题,有了一根筷子,还要第二根筷子,但是同时不想放弃手里已有的筷子
4.循环等待.(类似于上图的哲学家问题)
1号等2号,2号等3号,3号等4号,4号等5号......这样进行循环等待
4.解决死锁问题
那么针对死锁我们最快的解决办法就是针对第四个条件,我们要破除循环等待,具体做法是啥呢
那就是针对锁排序,如果要同时获取多把锁,约定加锁顺序,先对序号小的加锁,再对大的加锁,约定,先获取小编号的锁,再获取大编号的锁
过程是这样的,首先 我们让每个哲学家拿起号比较小的筷子,先让2号拿筷子,那肯定拿1号筷子,3号拿2号筷子,以此类推
这样1号就没有筷子了,那么5号就先吃面条了,5号拿起5号筷子,吃完放下筷子,4号拿起4号筷子,吃上面条,也放下筷子,3号,2号以此类推,最后1号吃面条.
因此,要解决死锁问题,我们只要注意加锁顺序就行了,先对小编号的加锁,再对大编号的加锁.
A synchronized(locker1){ synchronized(locker2){ } } B synchronized(locker1){ synchronized(locker2){ } }
经过修改,发现不死锁了,为啥呢.因为:现在A中对1加锁,B想对1加锁,那么B就得阻塞等待,所以就得等到A执行完释放锁B才能执行
所以,我们平时在写代码的时候一定要注意加锁顺序!!!