多线程系列六之死锁
提示:本文主要讲述了死锁的三种典型情况
一、什么是死锁
1.死锁在程序猿开发环境中十分影响程序猿幸福感的一个问题,因为死锁的出现十分隐蔽,我们可能会感知不到,从而出现死锁。
2.死锁:死锁是指两个或更多的事物在同一资源的相互占用,并尝试获取对方的资源。
当很多进程因资源竞争而造成的一种僵局(都在互相等待对方释放锁),若没有外力的作用,这些进程都将处在等待之中。
二、死锁的三种典型
1.一个线程一把锁连续加锁两次
1.此时代码并没有报错,说明在Java中一个线程 一把锁synchronized可以连续加锁两次
2.另外Java中ReentrantLock也可以连续加锁
3.对于可以连续加锁两次的锁,我们称之为可重入锁。
2.两个线程两把锁
此时提供一个场景:
t1 和 t2 两个线程 他们分别有自己的锁locker1 和 locker2,两把线程都尝试获取对方的锁。
这个时候就会出现死锁。
运行结果:
1.此时没有打印任何日志,说明t1 和 t2线程都没有获取到对方的锁,发生了死锁
2.可以通过jconsole观察此时线程的情况。
3.M个线程N把锁
多个线程多把锁的最典型例子就是哲学家就餐问题
1.假设此时有五位哲学家相当于五个线程。
五个筷子相当于五把锁
2.哲学家有两种状态
第一种状态就是思考状态相当于阻塞等待状态
第二种状态就是拿起筷子吃饭阶段,此时相当于线程的执行阶段,执行阶段需要获取到哲学家左手和右手的筷子。
3.如果每个哲学家都拿起右手边的筷子,那么五个线程都会进入死锁阻塞等待。
如图所示
解决方案
1.右手拿编号小的,左手拿编号大的,从编号小的筷子开始
2.此时最上面那位哲学家不符合要求就会进入阻塞等待
3.因此最右边那位哲学家符合要求,拿到了两个筷子 开始执行,然后依次顺时针进行。
4.到了最上面那位科学家后,他身边的两把锁都已经释放,再拿起两边的筷子,执行任务即可 。
三、可重入与不可重入
1.在Java当中synchronized锁和ReentranLock锁都可以连续加锁两次。这个时候我们称这个锁是可重入锁。
2.在Java当中无法演示不可重入锁,但是并不意味着其他语言也是可重入的,例如在C++、Python、操作系统原生的加锁的API都是不可重入的。
我们把这种锁称为不可重入锁。
四、死锁的必要条件
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
对于上述所说的四点,上面三点都是锁的基本特性,无法改变的。
五、破除死锁
死锁是一个十分影响开发事情的问题,那么如何破除死锁呢?????
由于3点都是锁的基本特性,因此只能从循环等待出发来打破锁的必要条件,这样也就破除了死锁。
办法:可以结合哲学家就餐问题!!!!
多个线程多个锁,给每把锁加上编号0,1,2,3,4,5…,然后按照固定顺序,比如从小到大顺序来加锁,任意线程任意把锁,让线程都遵循这个循环,此时循环等待自然破除,也就破除了死锁。