我们使用加锁机制来确保线程安全,但如果过度地使用加锁,则可能导致锁顺序死锁。当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么他们将永远被阻塞。
在数据库系统的设中考虑了检测死锁以及从死锁中恢复。但它检测到一组事务发生了死锁时(通过在表示等待关系的有向图中搜索循环),将选择一个牺牲者并放弃这个事务
JVM在解决死锁问题方面并没有数据库服务那样强大。当一组线程发生死锁时,“游戏”将到此结束。根据线程完成工作的不同,可能造成应用程序完全停止,或者某个特定的子系统停止。
顺序死锁:
死锁的原因是:两个线程试图以不同的顺序来获得相同的锁。
如果按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性,因此也就不会产生死锁。
但是有时候,并不能清楚地知道是否在锁顺序上有足够的控制权来避免死锁的发生。
如考虑以下情景:
银行转账行为,需要将资金由一个账户转入另一个账户。在开始转账前要先获得这两个账户的Acount对象的锁,以确保通过原子的方式来更新两个账户中的余额。
public void transferMoney(Acoount fromAccount,Account toAccount,int amount){
synchronized(fromAccount){
synchronized(toAccount){
...
transfer money from fromAccount to toAccount
...
}
}
}
以上是 线程不安全的,如果两个线程同时调用transferMoney,其中一个线程从X向Y转账,另一个线程从Y向X转账,那么就会发生死锁。
解决方式是:可以使用System.identityHashCode,该方法将返回由object.hashCode返回的值。
修改以上代码如下:
public void transferMoney(Acoount fromAccount,Account toAccount,int amount){
int fromHash = System.identityHashCode(fromAccount);
int toHash = System.identityHashCode(toAccount);
if(fromHash > toHash){
synchronized(fromAccount){
synchronized(toAccount){
...
transfer money from fromAccount to toAccount
...
}
}
}else{
synchronized(toAccount){
synchronized(fromAccount){
...
transfer money from fromAccount to toAccount
...
}
}
}
}
这样无论传入参数的位置如何,获得锁的顺序都是一样的。
在协作对象之间发生死锁
资源死锁
当多个线程相互持有彼此正在等待的锁而又不释放自己已持有的锁时会发生死锁, 当他们在相同的资源集合上等待时,也会发生死锁
死锁的避免与诊断
如果必须获得多个锁,那么在设计时必须考虑锁的顺序:尽量减少潜在的而加锁交互数量,将获取锁时需要遵循的协议写入正式的文档并始终遵循这些协议
必要时候使用支持定时的锁,及显示的使用Lock类中的定时tryLock功能来代替内置锁机制。当使用内置锁时,只要没有获得锁,就会永远的等待下去,而显示锁则可以指定一个超时时限,在等待了一定时间以后,如果还没有成功获得锁,那么久放弃,并返回一个失败信息。
还可以通过线程的转储信息来分析死锁发生的信息。