zoukankan      html  css  js  c++  java
  • 死锁问题

    1.死锁问题

    现实世界里,账户转账操作是支持并发的,而且绝对是真正的并行,银行所有的窗口都可以做转账操作。
    只要我们能仿照现实世界做转账操作,串行的问题就解决了。
    我们试想在古代,没有信息化,账户的存在形式真的就是一个账本,而且每个账户都有一个账本,这些账本都统一存放在文件架上。银行柜员在给我们做转账时,要去文件架上把转出账本和转入账本都拿到手,然后做转账。这个柜员在拿账本的时候可能遇到以下三种情况:文件架上恰好有转出账本和转入账本,那就同时拿走;如果文件架上只有转出账本和转入账本之一,那这个柜员就先把文件架上有的账本拿到手,同时等着其他柜员把另外一个账本送回来;转出账本和转入账本都没有,那这个柜员就等着两个账本都被送回来。上面这个过程在编程的世界里怎么实现呢?其实用两把锁就实现了,转出账本一把,转入账本另一把。在 transfer() 方法内部,我们首先尝试锁定转出账户 this(先把转出账本拿到手),然后尝试锁定转入账户 target(再把转入账本拿到手),只有当两者都成功时,才执行转账操作。这个逻辑可以图形化为下图这个样子。

    而至于详细的代码实现,如下所示。经过这样的优化后,账户 A 转账户 B 和账户 C 转账户 D 这两个转账操作就可以并行了。

    
    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线程取到锁A,B线程取到锁B,都死等下去

    2.如何预防死锁

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

    其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的,到底如何做呢?
    对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。(新抽象单例的管理员Allocator,一次取多个锁的操作,只有他能做)
    对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。(synchronized 实现不了,得用并发包里的Lock相关技术)
    对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。

    
    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)
        }
      } 
    }
    
  • 相关阅读:
    oracle学习之数据库数据保存成文件
    秦时明月这部烂电视
    Oracle学习之buffer cache(copy过来的文本,待补充笔记)
    Oracle学习之shared pool及sga的大小的设置
    Oracle学习之sql共享以及4031解决方法
    Oracle学习之内存块组成结构及4031错误分析
    树形逻辑套路总结
    HashMap并发分析
    JVM常用指标查询
    读Lock-Free论文实践
  • 原文地址:https://www.cnblogs.com/PythonOrg/p/14061532.html
Copyright © 2011-2022 走看看