并发编程之死锁

本文探讨了并发编程中死锁现象的产生条件,如占有且等待、不可抢占和循环等待,并提供了破坏这些条件的方法,包括一次性申请所有资源、使用等待-通知机制和资源排序。通过Account类的转账操作实例展示了如何避免死锁。
摘要由CSDN通过智能技术生成

并发编程之死锁

接上一篇文章转账操作死锁问题:https://blog.csdn.net/weixin_39707461/article/details/133893488?spm=1001.2014.3001.5502

class Account {
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    // 锁定转出账户
    synchronized(this) {   //1)         
      // 锁定转入账户
      synchronized(target) {       //2)      
        if (this.balance > amt) {
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}

回忆:
线程1(A给B转账):1)锁住 账户A,2)锁住 账户B;
线程2(B给C转账):1)锁住 账户B,2)锁住 账户A;
线程1 与 线程2 互斥,因为不可能都同时获取到账户A和账户B的锁,所以不可以发生在同一时刻。

总结:该方法可以保证并发安全。但是,如果当 线程1 获取到 账户A 的锁(等待线程2释放 账户B 的锁),线程2 获取到 账户B 的锁(等待线程2释放 账户B 的锁)。两个线程就陷入了死锁状态。

死锁产生的几个条件

1:占有且等待,线程1 已经取得共享资源 账户A 对象,在等待共享资源 账户B 对象的时候,不释放共享资源 账户A;

2:不可抢占,其他线程不能强行抢占 线程1 占有的资源;

3:循环等待,线程1 等待 线程2 占有的资源,线程2 等待 线程1 占有的资源,就是循环等待。

我们只要破坏其中一个条件,就可以成功避免死锁的发生。

破坏 占有且等待 条件

对于“占用且等待”这个条件,如果可以一次性申请所有的资源,这样就不存在等待了。

实现思路:定义一个全局唯一的资源分配者,它有两个重要功能,分别是:同时申请资源 apply() 和同时释放资源 free()

// 全局唯一的分配者
class Allocator {
  private List<Object> als = new ArrayList<>();
  
  // 一次性申请所有资源,该方法只能是串行的
  synchronized boolean apply(Object accountFrom, Object accountTo) {
    //如果资源已经被 其它线程申请,则申请失败
    if(als.contains(accountFrom) || als.contains(accountTo)){
      return false;  
    } else {
      //两个资源都是空闲的,允许申请
      als.add(accountFrom);
      als.add(accountTo);  
    }
    return true;
  }
  
  // 归还资源
  synchronized void free(Object accountFrom, Object accountTo){
    als.remove(from);
    als.remove(to);
  }
}

class Account {
  // actr应该为单例
  private Allocator actr;
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    // 一次性申请转出账户和转入账户,直到成功,注意这里死循环获取的缺点是一直获取不到,会一直占用CPU资源
    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)
    }
  } 
}

破坏不可抢占条件

使用 等待-通知 机制优化, 使用 synchronized 配合 wait()、notify()、notifyAll() 这三个方法可以快速实现这种机制:

class Allocator {
private List<Object> als;
// 一次性申请所有资源
synchronized void apply(
  Object from, Object to){
  // 经典写法
  while(als.contains(from) || als.contains(to)){
    try{
      //主动让出锁
      wait();
    }catch(Exception e){
    }   
  } 
  als.add(from);
  als.add(to);  
}
// 归还资源
synchronized void free(Object from, Object to){
  als.remove(from);
  als.remove(to);
  notifyAll();
}
}

破坏循环等待条件

破坏这个条件,需要对资源进行排序,然后按序申请资源。

这个实现非常简单,我们假设每个账户都有不同的属性 id,这个 id 可以作为排序字段,申请的时候,我们可以按照从小到大的顺序来申请。这样就不存在“循环”等待了。

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;
        }
      }
    }
  } 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值