zoukankan      html  css  js  c++  java
  • 并发编程实战(二) --- 如何避免死锁

    死锁了怎么办?

    前面说使用Account.class作为转账的互斥锁,这种情况下所有的操作都串行化,性能太差,这个时候需要提升性能,肯定不能使用这种方案.

    现实化转账问题

    假设某个账户的所有操作都在账本中,那转账操作需要两个账户,这个时候有三种情况:

    1. 两个账户的账本都存在,这个时候一起拿走
    2. 两个账户的账本只存在其一,先拿一个,等待其他人把剩余一本送过来
    3. 两个账户的账本都没有,等待其他人把两个账本都送回来

    上面的逻辑其实就是使用两把锁实现,图形化:
    transfer

    代码实现如下:

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

    这个其实就是细粒度锁. 细粒度锁提高并行度,是性能优化的一个重要手段.,但是天下没有免费的午餐,这种细粒度锁可能会导致死锁的情况发生.也就是假如现在A转账100给B,由张三做这个转账业务;B转账给A100元,由李四完成这个转账业务,这个时候张三拿到A的账户本,同一时刻李四拿到B的账户本,这个时候张三等待李四的B账户本,李四等待张三的A账户本,两人都不会送回来,就产生的死等,死等就是变成领域的死锁.死锁是指一组互相竞争资源的线程因互相等待,导致永久阻塞的现象

    预防死锁

    死锁一旦产生是没有办法解决的,只能重启应用. 所以解决死锁的最好办法就是避免死锁,如何避免死锁,那就要从产生死锁的条件入手:

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

    四个添加同时满足就会产生死锁,只要能破坏掉有一个条件,死锁就不会产生.共享资源是没有办法破坏,也就是互斥是没有办法解决,锁的目的就是为了互斥.

    1. 占有且等待: 一次性申请所有的资源就可以解决
    2. 不可抢占: 占用部分资源后获取不到后续资源就释放掉前面获取的资源,就可以解决
    3. 循环等待: 按照序号申请资源来预防,也就是说给每个资源标记一个序号,没次加锁的时候都先获取资源序号小的,这样有顺序就不会出现循环等待
    破坏占用且等待

    只需要同时申请资源就可以,同时申请这个操作是一个临界区,需要一个Java类来管理这个临界区,也就是定义一个角色,这个角色的两个重要功能就是同时申请资源apply()和同时释放资源free(),并且这个类是单例的.其实本质就是设置一个管理员,只有管理员有权限去分配资源,其他普通用户只能去管理员那取资源,一个人操作就不会产生死锁了.

    代码实现如下:

    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)
        }
      } 
    }
    
    
    破坏不可抢占条件

    这个的核心是释放掉已占有的资源,这个synchronized是做不到,因为synchronized申请资源的时候如果申请不到就直接进入阻塞,阻塞状态啥也干不了.

    这个时候就需要java.util.concurrent包下提供的Lock,这个等学到的时候再总结.

    破坏循环等待条件

    这个就需要一个id值了,保护加锁的顺序是从序号小的资源开始.

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

    保证课加锁的顺序,就不会出现循环等待了.

    编程世界其实是和现实世界有所关联的,编程不就是为了解决现实生活中的问题吗? 上面的解决死锁的两个方案,那个更好呢? 其实破坏循环等待条件的成本要比破坏占有且等待的成本要低,后者也锁定了所有账户并且使用了死循环.相对来说,前者的成本低,但是不是绝对的,只是转账的这个例子中,破坏循环等待的成本比较低.

  • 相关阅读:
    POJ 1113 Wall
    POJ 2159 Ancient Cipher
    POJ 3253 Fence Repair
    HDU 5444 Elven Postman
    HDU 5432 Pyramid Split
    数据库 组合查询
    数据库 简单的数据查询
    数据库 聚合函数与分组
    数据库 使用DML语句更改数据
    数据库的数据完整性
  • 原文地址:https://www.cnblogs.com/wadmwz/p/10504201.html
Copyright © 2011-2022 走看看