目录
🐕一、死锁
🐇1.概念
🐇1.概念
多个线程在争夺资源时,陷入了僵持状态,都无法进行下去,都在等待对方释放资源,如果不干涉,将一直僵持下去
🐇2.死锁的三个典型情况
通过这三个典型情况,可以帮助理解死锁
1).一个线程一把锁,连续加锁两次,如果锁是不可重入锁就会死锁
2).两个线程两把锁,t1和t2先分别针对锁A和锁B进行加锁,再尝试获取对方的锁.
t1先拿到锁A,然后获取锁B,t2先拿到锁B,然后获取锁A,此时就会发生死锁.
举个例子:有一天小马和女朋友出去吃饺子,两人都喜欢用酱油和醋蘸着饺子吃,饺子上好以
后,小马拿起了醋,女朋友拿起了酱油,然后女朋友对小马说:你把醋给我,我用完了就给你. 然
后,小马说:你把酱油给我,我用完了给你.如果此时两个人互不相让,此时就发生死锁.
针对这种情况,通过下面代码来加深理解:
public class ThreadDemo21 { public static void main(String[] args) { // 创建两个实例:jiangyou和cu Object jiangyou = new Object(); Object cu = new Object(); // 创建两个线程 Thread t1 = new Thread(() -> { // 先拿到cu synchronized (cu) { try { // 为了让t2拿到jiangyou Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (jiangyou) { System.out.println("小马拿到了醋和酱油"); } } }); Thread t2 = new Thread(() -> { // 先拿到cu synchronized (jiangyou) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (cu) { System.out.println("女朋友拿到了醋和酱油"); } } }); t1.start(); t2.start(); } }此时运行代码,会发现程序无任何运行结果,说明没有线程拿到两把锁.
使用jconsole观察当前线程的情况
如果打开jconsole没有进程的话,以管理员身份运行
3).多个线程多把锁
经典案例:哲学家就餐问题
问题描述:假设现在餐桌上有五个哲学家,每个哲学家的面前都放着一碗面条,桌子上有5根筷子,每个哲学家都需要两根筷子才可以吃面条,每个哲学家都有两种状态:1.思考人生(相当于线程的阻塞状态) 2.拿起筷子吃面条(相当于线程获取到锁,执行指令) 由于操作系统的随机调度,这5个哲学家随时都可能想吃面条或者思考人生,要想吃面条必须拿起左右手的两根筷子,情况图如下:
假设在某一时刻,发生极端情况,此时就会发生死锁
例如:同一时刻,所有哲学家都拿起左手的筷子,此时哲学家们都拿不到右手的筷子,都需要等待右边的哲学家将筷子放下.
🐇3.死锁的必要条件
死锁有四个必要条件:
1).互斥使用
线程1拿到了锁,线程2就只能阻塞等待,无法使用锁.
2).不可抢占
线程1拿到锁后,线程2不能强夺锁,只能阻塞等待,等待线程1释放锁
3).请求和保持
线程1拿到锁A后,再尝试获取锁B,此时锁A还是保持的(并不会因为要获取锁B就释放锁A)
4).循环等待
线程1尝试获取锁A和锁B 线程2尝试获取锁B和锁A
线程1在获取锁B的时候等待线程2释放锁B,线程2在获取锁A的时候等待线程1释放锁A
🐇4.如何破除死锁
在死锁的四个必要条件中,前三个都是锁的基本特性,我们无法更改,所以说破除死锁的突破口就是:循环等待
如何破除循环等待呢?
最简单的办法:给锁编号,然后指定一个固定的顺序(比如从小到大)来加锁,任意线程加多把锁的时候,都让线程遵守上述顺序,此时循环等待自然破除.
以哲学家就餐问题为例:破除循环等待,为每一个筷子编号,从小到大,然后从第一个哲学家开始,规定:每次先拿编号小的筷子,然后拿编号大的筷子.
此时,对于哲学家5来说,无法拿起筷子,因为编号1的筷子已经被哲学家1拿走了,此时哲学家5阻塞等待,此时,对于哲学家4来说,他就可以拿起编号为5的筷子,然后吃面条,吃完后,放下筷子,此时哲学家3可以吃面条,以此类推,最后死锁就会被破除了.
针对上面的两个线程两把锁的代码,破除循环等待的代码如下.
- public class ThreadDemo21 {
- public static void main(String[] args) {
- // 规定:jiangyou 是1号 cu 是2号 约定:先拿小的,再拿大的
- Object jiangyou = new Object();
- Object cu = new Object();
- // 创建两个线程
- Thread t1 = new Thread(() -> {
- // 先拿到cu
- synchronized (jiangyou) {
- try {
- // 为了让t2拿到jiangyou
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (cu) {
- System.out.println("小马拿到了醋和酱油");
- }
- }
- });
- Thread t2 = new Thread(() -> {
- // 先拿到cu
- synchronized (jiangyou) {
- try {
-
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (cu) {
- System.out.println("女朋友拿到了醋和酱油");
- }
- }
- });
- t1.start();
- t2.start();
- }
- }
此时运行代码:
死锁的问题就得到了解决.
还有一种办法破除死锁的办法,银行家算法,但是过于复杂,这里只介绍简单的解决死锁的办法