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

目录

​​​​​​​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个必要条件

  • 互斥,共享资源X和Y只能被一个线程占用;
  • 占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X;
  • 不可抢占,其他线程不能强行抢占线程T1占有的资源;
  • 循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待。

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破坏占用且等待条件,一次性申请全部资源

class Allocator {
  private List<Object> als = new ArrayList<>();
  // 一次性申请所有资源
  synchronized boolean apply(
    Object from, Object to){
    if(als.contains(from) ||
         als.contains(to)){
      return false;  
    } else {
      als.add(from);
      als.add(to);  
    }
    return true;
  }
  // 归还资源
  synchronized void free(
    Object from, Object to){
    als.remove(from);
    als.remove(to);
  }
}
class Account {
  // actr应该为单例
  private Allocator actr;
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    // 一次性申请转出账户和转入账户,直到成功
    while(!actr.apply(this, target))
      ;
    try{
      // 锁定转出账户
      synchronized(this){

        // 锁定转入账户
        synchronized(target){

          if (this.balance > amt){
            this.balance -= amt;
            target.balance += amt;
          }
        }
      }
    } finally {
      actr.free(this, target)
    }
  } 
}

5.2破坏循环等待条件

class Account {
  private int id;
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    Account left = this        
    Account right = target;    
    if (this.id > target.id) { 
      left = target;           
      right = this;            
    }                          
    // 锁定序号小的账户
    synchronized(left){
      // 锁定序号大的账户
      synchronized(right){ 
        if (this.balance > amt){
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}

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

  1. 设置超时时间
  2. 多食用并发类而不是自己设计类
  3. 尽量降低所得使用粒度,用不同的锁而不是同一个锁
  4. 如果能使用同步代码块,就不使用同步方法,自己指定锁对象
  5. 给你的线程起个有意义的名字,debug和排查的时事半功倍,框架和JDK都遵守这个最佳实践
  6. 避免锁的嵌套:MustDeadLock
  7. 分配资源前先看能不能收回来
  8. 尽量不要几个功能用同一把锁:专锁专用
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值