欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

如何避免死锁?我们有套路可循

程序员文章站 2023-04-04 15:01:45
写在前面 上一篇文章 "共享资源那么多,如何用一把锁保护多个资源?" 文章我们谈到了银行转账经典案例,其中有两个问题: 1. 单纯的用 synchronized 方法起不到保护作用(不能保护 target) 2. 用 Account.class 锁方案,锁的粒度又过大,导致涉及到账户的所有操作(取款 ......

写在前面

上一篇文章 文章我们谈到了银行转账经典案例,其中有两个问题:

  1. 单纯的用 synchronized 方法起不到保护作用(不能保护 target)
  2. 用 account.class 锁方案,锁的粒度又过大,导致涉及到账户的所有操作(取款,转账,修改密码等)都会变成串行操作

如何解决这两个问题呢?咱们先换好衣服穿越回到过去寻找一下钱庄,一起透过现象看本质,dengdeng deng.......

如何避免死锁?我们有套路可循

来到钱庄,告诉柜员你要给铁蛋儿转 100 铜钱,这时柜员转身在墙上寻找你和铁蛋儿的账本,此时柜员可能面临三种情况:

  1. 理想状态: 你和铁蛋儿的账本都是空闲状态,一起拿回来,在你的账本上减 100 铜钱,在铁蛋儿账本上加 100 铜钱,柜员转身将账本挂回到墙上,完成你的业务
  2. 尴尬状态: 你的账本在,铁蛋儿的账本被其他柜员拿出去给别人转账,你要等待其他柜员把铁蛋儿的账本归还
  3. 抓狂状态: 你的账本不在,铁蛋儿的账本也不在,你只能等待两个账本都归还

放慢柜员的取账本操作,他一定是先拿到你的账本,然后再去拿铁蛋儿的账本,两个账本都拿到(理想状态)之后才能完成转账,用程序模型来描述一下这个拿取账本的过程:

如何避免死锁?我们有套路可循

我们继续用程序代码描述一下上面这个模型:

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

这个解决方案看起来很完美,解决了文章开头说的两个问题,但真是这样吗?


我们刚刚说过的理想状态是钱庄只有一个柜员(既单线程)。随着钱庄规模变大,墙上早已挂了非常多个账本,钱庄为了应对繁忙的业务,开通了多个窗口,此时有多个柜员(多线程)处理钱庄业务。

如何避免死锁?我们有套路可循

柜员 1 正在办理给铁蛋儿转账的业务,但只拿到了你的账本;柜员 2 正在办理铁蛋儿给你转账的业务,但只拿到了铁蛋儿的账本,此时双方出现了尴尬状态,两位柜员都在等待对方归还账本为当前客户办理转账业务。

如何避免死锁?我们有套路可循

现实中柜员会沟通,喊出一嗓子 老铁,铁蛋儿的账本先给我用一下,用完还给你,但程序却没这么智能,synchronized 内置锁非常执着,它会告诉你「死等」的道理,最终出现死锁

java 有了 synchronized 内置锁,还发明了显示锁 lock,是不是就为了治一治 synchronized 「死等」的执着呢?