05 | 一不小心就死锁了,怎么办?

📅 2026/6/18 16:46:03
05 | 一不小心就死锁了,怎么办?
第一部分:并发理论基础05 | 一不小心就死锁了,怎么办?文章目录第一部分:并发理论基础05 | 一不小心就死锁了,怎么办?向现实世界要答案没有免费的午餐如何预防死锁1.破坏占用且等待条件2.破坏不可抢占条件3.破坏循环等待条件总结课后思考在上一篇文章中,我们用 Account.class 作为互斥锁,来解决银行业务里面的转账问题,虽然这个方案不存在并发问题,但是所有账户的转账操作都是串行的,例如账户 A 转账户 B、账户 C 转账户 D 这两个转账操作现实世界里是可以并行的,但是在这个方案里却被串行化了,这样的话,性能太差。试想互联网支付盛行的当下,8 亿网民每人每天一笔交易,每天就是 8 亿笔交易;每笔交易都对应着一次转账操作,8 亿笔交易就是 8 亿次转账操作,也就是说平均到每秒就是近 1 万次转账操作,若所有的转账操作都串行,性能完全不能接受。那下面我们就尝试着把性能提升一下。问题:Account不是可以new吗,我感觉不是串行啊答案:你的困惑非常正常!这是一个很经典的误解。让我详细解释为什么锁 Account.class 会导致所有转账操作串行化。关键理解:Account.class 是唯一的。虽然你创建了多个 Account 对象,但是 Account.class 在整个 JVM 中只有唯一的一个!向现实世界要答案现实世界里,账户转账操作是支持并发的,而且绝对是真正的并行,银行所有的窗口都可以做转账操作。只要我们能仿照现实世界做转账操作,串行的问题就解决了。我们试想在古代,没有信息化,账户的存在形式真的就是一个账本,而且每个账户都有一个账本,这些账本都统一存放在文件架上。银行柜员在给我们做转账时,要去文件架上把转出账本和转入账本都拿到手,然后做转账。这个柜员在拿账本的时候可能遇到以下三种情况:1.文件架上恰好有转出账本和转入账本,那就同时拿走;2.如果文件架上只有转出账本和转入账本之一,那这个柜员就先把文件架上有的账本拿到手,同时等着其他柜员把另外一个账本送回来;3.转出账本和转入账本都没有,那这个柜员就等着两个账本都被送回来。上面这个过程在编程的世界里怎么实现呢?其实用两把锁就实现了,转出账本一把,转入账本另一把。在 transfer() 方法内部,我们首先尝试锁定转出账户 this(先把转出账本拿到手),然后尝试锁定转入账户 target(再把转入账本拿到手),只有当两者都成功时,才执行转账操作。这个逻辑可以图形化为下图这个样子。而至于详细的代码实现,如下所示。经过这样的优化后,账户 A 转账户 B 和账户 C 转账户 D 这两个转账操作就可以并行了。class Account { private int balance; // 转账 void transfer(Account target, int amt){ // 锁定转出账户 synchronized(this) { // 锁定转入账户 synchronized(target) {