【多线程】死锁详解

在这里插入图片描述

一、什么是死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,他们都将无法进行下去。也就是说两个线程在拥有锁的情况下,又尝试获取对方的锁,从而导致程序进入阻塞状态。
在这里插入图片描述
手撕死锁代码(面试常考)~

import java.util.concurrent.TimeUnit;

/**
 * 死锁示例
 */
public class ThreadDemo17 {
    public static void main(String[] args) {
        Object lockA=new Object();
        Object lockB=new Object();

        Thread  t1=new Thread(()->{
            //1.占有一把锁(锁A)
            synchronized (lockA){
                System.out.println("线程1:获取锁A");
                //休眠1s(让线程2有时间获取锁B)
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //2.试图获取另一个线程的锁B
                synchronized (lockB){
                    System.out.println("线程1:获取锁B");
                }
            }

        });
        t1.start();

        Thread t2=new Thread(()->{
            //1.占有一把锁(锁B)
            synchronized (lockB){
                System.out.println("线程2:获取锁B");
                //休眠1s(让线程2有时间获取锁B)
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //2.试图获取另一个线程的锁A
                synchronized (lockA){
                    System.out.println("线程2:获取锁A");
                }
            }
        });
        t2.start();
    }
}

怎样查看死锁呢?我们可以使用jvsualvm/jmc/jconsole查看死锁,下面我们展示一下用jvsualvm查看死锁。
在这里插入图片描述
在这里插入图片描述

我们可以看到Thread0和Thread1有死锁出现。

二、死锁产生的必要因素

1. 互斥条件

一个资源同一时间只能被一个线程占用。

2.不可剥夺条件

一个资源被占用之后,如果不是拥有资源的线程释放,那么其他线程得不到此资源。

3.请求并持有条件

当一个线程拥有了某个资源之后,还不满足,又在请求其他资源。

4.环路等待条件

多个线程在请求资源的情况下,形成了环路链。

以上四个条件是导致死锁的必要条件,要形成死锁,以上4个条件缺一不可

三、解决死锁

3.1 解决死锁方案分析

打破形成死锁的一个或者是多个条件即可。
接下来我们来分析一下,哪些条件是可以打破的,哪些不行?

  • 互斥条件:系统特性,不可打破。
  • 不可剥夺条件:系统特性,不可打破。
  • 请求并持有条件:可以打破。
  • 环路等待条件:可以打破。

通过上述分析,我们可以通过打破环路等待条件和请求并持有条件来解决死锁问题。

3.2 解决死锁方案1:顺序锁

所谓顺序锁就是有顺序的获取锁,从而避免环路等待条件,解决死锁问题。如下图所示:
在这里插入图片描述
我们让线程1和线程2获取锁的顺序统一,也就是让线程1和线程2同时执行,先获取锁A,但是只能有一个线程可以获取到锁A,没有获取到锁A的线程等待获取锁A,获取到锁A的线程继续执行获取锁B,因为没有线程争抢和拥有锁B,所以拥有锁A的线程会顺利获取到锁B,之后执行相应的业务代码,再将锁资源全部释放(先释放锁B,再释放锁A),然后另一个等待获取锁A的线程就可以顺利得到锁资源,执行后续代码,这样就解决了死锁问题。

讲清楚了解决方案,我们上代码!

import java.util.concurrent.TimeUnit;

/**
 * 解决死锁方案:破环环路等待条件
 */
public class UnDead {
    public static void main(String[] args) {
        Object lockA=new Object();
        Object lockB=new Object();

        Thread  t1=new Thread(()->{
            synchronized (lockA){
                System.out.println("线程1:获取锁A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){
                    System.out.println("线程1:获取锁B");
                    //业务代码
                    System.out.println("线程1:释放锁B");
                }
                System.out.println("线程1:释放锁A");
            }

        });
        t1.start();

        Thread t2=new Thread(()->{
            synchronized (lockA){
                System.out.println("线程2:获取锁A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){
                    System.out.println("线程2:获取锁B");
                    //业务代码
                    System.out.println("线程1:释放锁B");
                }
                System.out.println("线程1:释放锁A");
            }
        });
        t2.start();
    }
}

在这里插入图片描述

3.3 解决死锁的方案3:轮询锁

轮询锁是通过打破请求并持有条件来解决死锁问题的,实现思路就是通过轮询来尝试获取锁,如果有一个锁获取失败,则释放当前线程拥有的所有锁,等待下一轮再尝试获取锁

上代码~~

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决死锁方案2:通过轮询锁破坏请求并持有条件
 */
public class UnDead2 {
    public static void main(String[] args) {
        Lock lockA=new ReentrantLock();
        Lock lockB=new ReentrantLock();
        //创建线程1(使用轮询锁)
        Thread t1=new Thread(()->{
            try {
                pollingLock(lockA,lockB);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();

        //创建线程2
        Thread t2=new Thread(()->{
            lockB.lock();
            System.out.println("线程2获取到锁B");
            try {
                Thread.sleep(1000);
                System.out.println("线程2等待获取锁A...");
                lockA.lock();
                try {
                    System.out.println("线程2获取到锁A");
                }finally {
                    lockA.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lockB.unlock();
            }
        });
        t2.start();
    }

    /**
     * 轮询锁
     * @param lockA
     * @param lockB
     */
    private static void pollingLock(Lock lockA, Lock lockB) throws InterruptedException {
        while(true){
            if(lockA.tryLock()){//尝试获取锁A
                System.out.println("线程1获取到锁A");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程1等待获取锁B...");
                    if (lockB.tryLock()) {
                        try {
                            System.out.println("线程1获取到锁B");
                        }finally {
                            lockB.unlock();
                            System.out.println("线程1释放锁B");
                            break;
                        }
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lockA.unlock();
                    System.out.println("线程1释放锁A");
                }
            }
            //等待1s继续执行
            Thread.sleep(1000);
        }
    }
}

在这里插入图片描述

以上就是死锁的产生以及解决方案啦,如果觉得对你有帮助就点个赞支持一下吧~

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值