Day284&285&286&287.死锁 -Juc

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day284&285&286&287.死锁 -Juc相关的知识,希望对你有一定的参考价值。

死锁

一、死锁的定义&危害

1、死锁是什么

  • 发生在并发

  • 互不想让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法进行千金,导致程序陷入无尽的阻塞,这就是死锁。

image-20210531204828143

  • 多个线程造成死锁的情况
    • 如果多个线程之间的依赖关系是环形,存在环路的锁的依赖关系,那么也可能会发送死锁

image-20210531205442271


2、死锁的影响

死锁的影响在不同的系统中是不一样的,这取决于系统对死锁的处理能力

  • 数据库中: 检测并放弃事务

  • JVM中: 无法自动处理

3、死锁的危害

image-20210531211007454


二、发生死锁的例子

1、最简单的情况

  • 代码
/******
 @author 阿昌
 @create 2021-05-31 21:16
  *******
  *      必定发生死锁的情况
 */
public class MustDeadLock implements Runnable {
    int flag = 1;//标记位
    static Object lock1 = new Object();
    static Object lock2 = new Object();

    public static void main(String[] args) {
        MustDeadLock r1 = new MustDeadLock();
        MustDeadLock r2 = new MustDeadLock();
        r1.flag=1;
        r2.flag=0;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);

        thread1.start();
        thread2.start();
    }

    @Override
    public void run() {
        System.out.println("flag= " + flag);
        if (flag == 1) {
            synchronized (lock1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println(flag);
                }
            }
        }
        if (flag == 0) {
            synchronized (lock2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println(flag);
                }
            }

        }
    }

}

image-20210531215001186


  • 分析

当thread1拿到lock1后休眠500ms,在此期间,thread2拿到了lock2后休眠500ms;

这次thread1视图想去获取lock2,但是lock2在thread2,没有被thread2释放,因此thread1会陷入阻塞,而thread会去那lock1,但是lock在thread1手上,没有释放,所以他们thread1和thread2都进入了阻塞,相互等待。所以进入了死锁


2、实际生产的转账案例

  • 正常成功的情况:↓↓↓
public class TransferMoney implements Runnable {

    Integer flag = 1;

    static Account a = new Account(500);
    static Account b = new Account(500);

    //主函数
    public static void main(String[] args) throws InterruptedException {
        TransferMoney t1 = new TransferMoney();
        TransferMoney t2 = new TransferMoney();
        t1.flag = 1;
        t1.flag = 0;

        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);

        thread1.start();
        thread2.start();
        thread1.join();
        thread1.join();
        System.out.println("a的余额为:"+a.balance);
        System.out.println("a的余额为:"+b.balance);
    }
    
    @Override
    public void run() {
        if (flag == 1) {
            transferMoney(a, b, 200);
        }
        if (flag == 0) {
            transferMoney(b, a, 200);
        }
    }
    
    //转账
    private void transferMoney(Account from, Account to, int amount) {
        synchronized (from) {
            synchronized (to) {
                if (from.balance - amount < 0) {
                    System.out.println("余额不足");
                }

                from.balance -= amount;
                to.balance += amount;
                System.out.println("转账成功");
            }
        }
    }

    static class Account {
        //余额
        int balance;

        public Account(int balance) {
            this.balance = balance;
        }
    }
}

image-20210531215241280


  • 出现死锁的情况:↓↓↓
//转账
private void transferMoney(Account from, Account to, int amount) {
    synchronized (from) {
        //加入线程睡眠500ms
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        synchronized (to) {
            if (from.balance - amount < 0) {
                System.out.println("余额不足");
            }

            from.balance -= amount;
            to.balance += amount;
            System.out.println("转账成功");
        }
    }
}

陷入死锁…

image-20210531215513629


  • 分析

当线程1执行transferMoney()方法的时候,他拿到from锁,也就是里面的类成员变量a;

进过500ms,这个期间线程2进来执行transferMoney()方法,拿到from锁,也就是类成员变量b

接下来500ms之后线程1继续执行,但是他要拿到to锁,也就是他的b成员变量,但是已经被线程1拿过去当他的from锁了

线程2接下来拿他的to锁,也就是a成员变量,但是他已经被线程1拿着了,因为a成员变量是线程1的from锁;

所以进入了死锁的情况


3、模拟多人随机转账

public class MultiTransferMoney {

    private static final int NUM_ACCOUNTS = 5000;//账号数
    private static final int NUM_MONEY = 1000;//余额
    private static final int NUM_ITERATIONS = 10000000;//转账次数
    private static final int NUM_THREADS = 20;//转账人数

    public static void main(String[] args) {
        Random random = new Random();
        TransferMoney.Account[] accounts = new TransferMoney.Account[NUM_ACCOUNTS];

        //初始化
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = new TransferMoney.Account(NUM_MONEY);
        }

        //转账类
        class TransferThread extends Thread{
            @Override
            public void run() {
                for (int i = 0; i < NUM_ITERATIONS; i++) {
                    //随机下标
                    int fromAcct = random.nextInt(NUM_ACCOUNTS);
                    int toAcct = random.nextInt(NUM_ACCOUNTS);
                    int amount = random.nextInt(NUM_ACCOUNTS);

                    TransferMoney.transferMoney(accounts[fromAcct], accounts[toAcct],amount);
                }
                System.out.println("程序结束!!!!");
            }
        }

        //线程数
        for (int i = 0; i < NUM_THREADS; i++) {
            new TransferThread().start();
        }

    }
}

运行了很久,发现不再打印,出现死锁!!!

image-20210601205312993

说明

在多人同时转账的情况下,虽然很人数很多,但只要有发生死锁的风险,就有可能导致,程序陷入死锁


三、死锁发生的必要条件

1、互斥条件

我这里有锁,我拿来了这锁之后,其他线程就不能拿到他,除非我释放了他


2、请求与保持条件

线程1请求一把锁,但是他又保持不释放一把锁


3、不剥夺条件

没有外界干扰结束死锁条件,不会想数据库一样去干扰,仲裁结束


4、循环等待条件

两个线程等待条件,就是你等我释放,我等你释放;

多个线程等待条件,就是A等B,B等C,C等D,D等E,E等F,F等A,无尽不释放,无尽陷入等待;构成环路


以上条件:缺一不可只需要破解以上4个条件任意一个条件,这个死锁就不会发生了!!


四、如何定位死锁的位置

1、jstack命令

通过使用java自带的jstack命令,来查找我们项目中的死锁问题

${JAVA_HOME}/bin/jstack 8359
#javahome下的jastack命令 进程的pid

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Pg3WYQM-1622556847634)(http://qskpmm1b6.hn-bkt.clouddn.com/20210601211629.png)]


执行结果图:↓↓↓

image-20210601212742979


2、ThreadMXBean代码

/******
 @author 阿昌
 @create 2021-06-01 21:41
 *******
 *      通过 ThreadMXBean 检测死锁
 */
public class ThreadMXBeanDetection implements Runnable{
    int flag = 1;//标记位
    static Object lock1 = new Object();
    static Object lock2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
        ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
        r1.flag=1;
        r2.flag=0;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);

        thread1.start();
        thread2.start();
        Thread.sleep(1000);

        //得到实例
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        //发现死锁
        if (deadlockedThreads != null && deadlockedThreads.length>0){
            //迭代
            for (long item : deadlockedThreads) {
                //获取线程信息
                ThreadInfo threadInfo = threadMXBean.getThreadInfo(item);
                //获取死锁线程的名字
                System.out.println("发现死锁:"+threadInfo.getThreadName());
            }
        }
    }

    @Override
    public void run() {
        System.out.println("flag= " + flag);
        if (flag == 1) {
            synchronized (lock1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println(flag);
                }
            }
        }
        if (flag == 0) {
            synchronized (lock2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println(flag);
                }
            }

        }
    }
}

image-20210601215013334


五、修复死锁策略

1、线上发生死锁应该怎么办

  • 线上问题都需要防范于未然,不造成损失地扑灭几乎已经是不可能

  • 保存案发现场,然后立即重启服务器

  • 暂时保存线上服务器的安全,然后在利用保存的信息,排除死锁,修改代码,重新发布


2、常见修复策略

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TR7EwwWx-1622556847647)(http://qskpmm1b6.hn-bkt.clouddn.com/20210601215548.png)]


3、转账时避免死锁

  • 实际上不在乎获取锁的顺序

  • 代码演示

通过System.identityHashCode(XXX)去获取对象的hash值,并进行比较他们的hash值来进行比较来决定拿锁的顺序

当发生hash碰撞时,就再加第三把锁给他们公平竞争

在实际中可以获取主键,来判断接下来的执行顺序,因为主键一般都是不重复唯一的

public class TransferMoney implements Runnable {

    Integer flag = 1;

    static Account a = new Account(500);
    static Account b = new Account(500);
    static Object lock = new Object();

    //主函数
    public static void main(String[] args) throws InterruptedException {
        TransferMoney t1 = new TransferMoney();
        TransferMoney t2 = new TransferMoney();
        t1.flag = 1;
        t1.flag = 0;

        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);

        thread1.start();
        thread2.start();
        thread1.join();
        thread1.join();
        System.out.println("a的余额为:" + a.balance);
        System.out.println("a的余额为:" + b.balance);
    }

    @Override
    public void run() {
        if (flag == 1) {
            transferMoney(a, b, 200);
        }
        if (flag == 0) {
            transferMoney(b, a, 200);
        }
    }

    //转账
    public static void transferMoney(Account from, Account to, int amount) {
        //帮助类
        class Helper {
            public void transfer() {
                if (from.balance - amount < 0) {
                    System.out.println("余额不足");
                }

                from.balance -= amount;
                to.balance += amount;
                System.out.println("转账成功");
            }
        }

        //获取对象hash值
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        if (fromHash < toHash) {
            synchronized (from) {
                synchronized (to) {
                    new Helper().transfer();
                }

            }
        }
        if (fromHash > toHash) {
            synchronized (to) {
                synchronized (from) {
                    new Helper().transfer();
                }
            }
        }
        //hash碰撞;设置加时赛,公平竞争
        if (fromHash == toHash) {
            synchronized (lock){
                synchronized (to) {
                    synchronized (from以上是关于Day284&285&286&287.死锁 -Juc的主要内容,如果未能解决你的问题,请参考以下文章

合并区间常考面试题

每日冲刺报告——Day2(Java-Team)

每日冲刺报告——Day3(Java-Team)

人生的诗·285~289节

《安富莱嵌入式周报》第286期:8bit浮点数规范,VxWorks火星探测器故障原因修复,Matter V1.0智能家居规范,Wireshark 4.0发布

第几天?(hdu2005)