我们先从简单的死锁最后到难一些的死锁问题开始展开讨论。
首先一个线程,一把锁,因为多次加锁而导致死锁问题,由于Java 的synchronized 实现了可重入锁,因此这个死锁问题就不存在了,意味着当一个线程拥有一把锁时,可以对这个锁进行多次加锁操作,而不会发生死锁问题,在上一篇文章中也仔细讨论了,这里不赘述了。
接下来就是两个线程,两把锁,两个线程都想获得对方的锁的时候,就会发生死锁问题,我们来看一下代码:
没想到,居然执行成功了!!!
为什么会执行成功呢?
因为t1.start() 的速度太快了,直接就获得了两把锁,t2 此时都还没开始执行就结束了。
但是这是一种大概率的情况,如果发生小概率事件,也就是死锁状态,程序就会一直卡住在这里
我们打开 Jconsole
我们看到两个线程进入了 BLOCKED 状态(阻塞状态),并且还能直到代码阻塞在第几行。
这就是死锁问题
最后n 个线程,m 把锁,这个就要拿出经典案例:哲学家就餐问题
现在图中有 6 位哲学家,每位哲学家的左手和右手两边各有一根筷子,哲学家此时会有两个事件随机发生,一个事件是拿起左手和右手的筷子吃面,另一个事件就是思考哲学问题(不吃面)
现在我们试想一个极端的情况,如果每一个哲学家此时都想吃面,他们同时拿起左边的筷子,这时候,没有一位哲学家是拿到一双筷子的,并且没有一位哲学家会放弃自己左手的筷子,都在等别人放下的筷子之后拿起来吃面,这时候谁都吃不成。
上面就是经典的哲学家问题,上面的哲学家可以类比我们的线程,筷子就是锁,虽然这种死锁发生的概率很低,但是我们还是要防患于未然。
这种死锁是循环等待导致的,A 等待 B,B 等待 C,C 等待 A,构成一个回路。
首先要回到锁的特性,因为锁是互斥的,一个线程拿到这个锁之后,另一个线程如果想要获得这个锁就必须阻塞等待。
锁是不可抢占的,不可剥夺的。 一个线程拿到这个锁之后,除非这个线程解锁释放这个锁,否则其他线程是无法暴力抢占获取的。
请求和保持。 这是发生在嵌套的情况下,也就是一个线程拿到 锁1 之后,在不释放锁 1 的前提下,申请获得锁 2 ,是有可能发生死锁的。
换一种说法就是,一个线程在获取到锁1 的时候,不想放弃这把锁1,也就是在保持锁 1 的情况下,发出锁 2 的请求。
循环等待。 多个线程,多把锁,在等待的过程中构成了循环,A 等待 B, B 也等待 A
首先回顾第一个和第二个产生死锁的原因,我们直到这个是锁的基本性质引出的,所以我们无力回天,除非你自己写一个锁的设置。
那我们来看一下第三个问题怎么解决,只要我们避免不要嵌套加锁就可以了,也就是用完锁 1 然后释放掉,最后再申请锁2 即可。
下面是死锁代码:
下面的破除嵌套之后的代码:
运行成功,没有发生死锁问题。
我们可以实现约定好加锁的顺序,就可以破除循环等待了。
例如,我们约定每个线程加锁的时候永远都是获得序号小的锁,然后获得序号大的锁。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/7215.html