转自:lemonGuo
死锁出现的场景
根据以上分析总结一下最坏的情况:
- synchronized(from):别的线程在等待from对象;
- synchronized(to):别的线程已经锁住了to对象;
因此,可能出现死锁的情况就是: transfer(a,b,100) 和 transfer(b,a,100)同时进行,这是对双方都很不利的情况:左边的抢走了a的锁,右边的抢走了b的锁。
形成死锁的条件
- 互斥等待:说白了也就是要在有锁的情况。
- hold and wait:拿到一个锁去等待另一个锁的状态,其实锁是很珍贵的资源,最好得到锁后尽快处理完毕将其释放。
- 循环等待:更槽糕的情况:例如线程1获得锁A在等待锁B,而线程2获取锁B在等待锁A。
- 无法剥夺的等待:在出现循环等待情况后,有的锁会出现超时后自动释放,但是若是一直等待,则必定死锁。
防止死锁的办法
若要避免死锁,根据以上四个产生死锁的原因,逐一破解即可:
-
破除互斥等待:不可!锁是保证线程安全的基本方法,无法实现。
-
破除hold and wait:可以!最关键的一步,就是一次性获取所有资源。例子中的from、to对象是分成两步获取的,从而会形成hold and wait情况,但是通常不允许同时锁两个对象,因此需要对代码做比较大的修改:
- 暴露一个锁名为getAmountLock,它是针对Amount的,from、to对象都可以getAmountLock,锁的时候可以带上一个短的超时,先锁住from再锁住to,当to锁不住的时候,把from锁放掉,过段时间再尝试。
- 或者在这两行的外面加一个全局的锁,保证可以同时拿到这两个锁,拿到这两个锁之后再将全局的锁释放掉。但是需要结合实际,银行系统中Amount的量很大,全局锁未必好,第一个方案较好。
-
破除循环等待:可以!按顺序获取资源。
- 让例子中的Amount之间有序,不要先synchronized对象from,再synchronized对象to,银行中AmountID肯定是惟一值,所以定制一个规则先处理较小值,这样即使同时互相转账,也不会出现死锁情况。
-
破除无法剥夺的等待:可以!加入超时。
- 设置超时时间5秒或者其它,但此方法并不理想,因为超时需要时间等待,耗时长,用户体验差。
总结
根据以上的分析,也许你认为第四种加入超时措施相对简单实现,但是如此一来不能使用synchronized,还要暴露一个锁;第二种 from.getAmountLock()
方法实现较复杂。
因此,第二种解决方法较好,即破除循环等待—–按顺序获取资源,出现并发时根据AmountID值先处理值较小的用户,但是这并不是最好的解决方法,因为此解决方法重点为按顺序获取资源,而银行账户中的ID顺序性是我假设出来的,并非实际。
所以,最理想的解决方法还是破除hold and wait,就是一次性获取所有资源!但是通常不允许同时锁两个对象,所以还是先锁住A再锁住B,当B锁不住的时候,把A锁放掉,过段时间再尝试。
完美的解决办法不存在!所以只能根据实际问题具体分析,选择一个折中的办法实现