Java教程

并发的死锁问题及解决方案

本文主要是介绍并发的死锁问题及解决方案,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

1.什么是死锁

2.发生死锁的例子

2.1简单的例子

2.2生产中的例子-转账

2.3模拟多人转账

3.死锁的4个必要条件

4.如何定位死锁

5.修复死锁的策略

5.1线上发生死锁怎么办?

5.2常见修复的策略

6.实际工作中如何避免死锁


1.什么是死锁

  • 发生在并发中
  • 互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁

2.发生死锁的例子

2.1简单的例子

  • 当类的对象flag=1时(T1),先锁定O1,睡眠500毫秒,然后锁定O2
  • 而T1在睡眠的时候另一个flag=0的对象(T2)启动,先锁定O2,睡眠500毫秒,等待T1释放O1
  • T1睡眠结束后需要锁定O2才能继续执行,而此时O2已被T2锁定
  • T2睡眠结束后需要锁定 O1才能继续执行,而此时O1已被T1锁定
  • T1和T2互相等待,都需要对方多订的资源才能继续执行,从而产生死锁
public class MustDeadLock implements Runnable {

    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

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

    @Override
    public void run() {
        if (flag == 1){
            synchronized (o1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println("r1成功拿到两把锁");
                }
            }
        }

        if (flag == 0){
            synchronized (o2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println("r2成功拿到两把锁");
                }
            }
        }
    }
}

2.2生产中的例子-转账

  • 需要两把锁
  • 获取两把锁成功,且余额大于0,则扣除转出人,增加收款人的余额,是原子操作
  • 顺序相反导致死锁
public class TransferMoney implements Runnable{

    int flag = 1;

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

    public static void main(String[] args) throws InterruptedException {
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        t1.join();
        t1.join();
        System.out.println("a的余额"+a.balance);
        System.out.println("b的余额"+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) {

        synchronized (from){
            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("转账成功");
            }
        }
    }

    static class Account{
        int balance;

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

}

2.3模拟多人转账

public class MultTransferMoney {
    public static final int NUM_ACCOUNTS = 500;
    public static final int NUM_MONEY = 1000;
    public static final int NUM_ITERATIONS = 1000;
    public static final int NUM_THREADS = 20;

    public static void main(String[] args) {
        final Random rnd = new Random();
        final Account[] accounts = new Account[NUM_ACCOUNTS];
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = new Account(NUM_MONEY);
        }
        class TransferThread extends Thread{
            @Override
            public void run(){
                for (int i = 0; i < NUM_ITERATIONS; i++) {
                    int fromAcct = rnd.nextInt(NUM_ACCOUNTS);
                    int toAcct = rnd.nextInt(NUM_ACCOUNTS);
                    int amount = rnd.nextInt(NUM_MONEY);
                    TransferMoney.transferMoney(accounts[fromAcct],accounts[toAcct],amount);
                }
            }
        }
        for (int i = 0; i < NUM_THREADS; i++) {
            new TransferThread().start();
        }
    }
}

3.死锁的4个必要条件

  • 互斥条件
  • 请求与保持条件
  • 不剥夺条件
  • 循环等待条件

4.如何定位死锁

使用ThreadMaxBean

public class ThreadMXBeanD implements Runnable{
    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String[] args) {
        ThreadMXBeanD r1 = new ThreadMXBeanD();
        ThreadMXBeanD r2 = new ThreadMXBeanD();
        r1.flag = 1;
        r2.flag = 0;
        new Thread(r1).start();
        new Thread(r2).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ThreadMXBean threadMXBean =  ManagementFactory.getThreadMXBean();
        final long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        if (deadlockedThreads != null && deadlockedThreads.length > 0){
            for (int i = 0; i < deadlockedThreads.length; i++) {
                final ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
                // 发生死锁Thread-1
                // 发生死锁Thread-0
                System.out.println("发生死锁"+threadInfo.getThreadName());
            }
        }

    }

    @Override
    public void run() {
        if (flag == 1){
            synchronized (o1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println("r1成功拿到两把锁");
                }
            }
        }

        if (flag == 0){
            synchronized (o2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println("r2成功拿到两把锁");
                }
            }
        }
    }
}

5.修复死锁的策略

5.1线上发生死锁怎么办?

  • 线上问题都需要防患于未然,不造成损失扑灭几乎已经是不可能
  • 保存案发现场然后重启服务器
  • 暂时保证线上服务的安全,然后在利用刚才保存的信息,排查死锁,修改代码,重新发版

5.2常见修复的策略

  • 避免策略:哲学家就餐的换手方案,转账换序方案
  • 检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,就打开死锁
  • 鸵鸟策略:如果我们发生死锁的概率非常低,那么我就直接忽略它,知道死锁的发生的时候,再人生修复

6.实际工作中如何避免死锁

  1. 设置超时时间
  2. 多食用并发类而不是自己设计类
  3. 尽量降低所得使用粒度,用不同的锁而不是同一个锁
  4. 如果能使用同步代码块,就不使用同步方法,自己指定锁对象
  5. 给你的线程起个有意义的名字,debug和排查的时事半功倍,框架和JDK都遵守这个最佳实践
  6. 避免锁的嵌套:MustDeadLock
  7. 分配资源前先看能不能收回来
  8. 尽量不要几个功能用同一把锁:专锁专用
这篇关于并发的死锁问题及解决方案的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!