1.死锁的定义及发生条件:
死锁就像是两个人过独木桥,在桥中间碰到了,谁也不想让步,结果谁也无法通过。
线程A占有锁L时想要获得锁M,而线程B占有锁M并尝试获得锁L,两个线程将永远等待下去,这种情况称为死锁(deadlock),或致命拥抱(the deadly embrace)。
在并发程序设计中,死锁 (deadlock) 是一种十分常见的逻辑错误。通过采用正确的编程方式,死锁的发生不难避免。
死锁发生的四个必要条件:
(1).互斥:存在这样一种资源,它在某个时刻只能被分配给一个执行绪(也称为线程)使用;
(2).持有:当请求的资源已被占用从而导致执行绪阻塞时,资源占用者不但无需释放该资源,而且还可以继续请求更多资源;
(3).不可剥夺:执行绪获得到的互斥资源不可被强行剥夺,换句话说,只有资源占用者自己才能释放资源;
(4).环形等待:若干执行绪以不同的次序获取互斥资源,从而形成环形等待的局面,即锁顺序死锁。
上面四个条件只要打破任何一个,死锁问题就可以解决。
数据库系统通过在表示正在等待关系的有向图上搜索循环来检测死锁,一旦发现死锁会牺牲一个调用者,使它退出事务释放资源,从而使其他事务能够继续进行。
JVM中线程发送死锁时,应用系统可能完全停止或者某个特定子系统停止,也可能是性能受到影响,死锁不但经常发生在高负载情况下,而且难以测试和重现,恢复应用程序健康状态的唯一方式是重启应用。
2.动态锁顺序死锁:
线程发送死锁必要条件中的环形等待容易产生锁顺序死锁,下面例子代码演示一个简单的锁顺序死锁:
public class LeftRightDeadlock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() {
synchronized (left) {
synchronized (right) {
doSomething();
}
}
}
public void rightLeft() {
synchronized (right) {
synchronized (left) {
doSomethingElse();
}
}
}
}
若一个线程调用leftRight方法,而另一个线程调用rightLeft方法,则很有可能发生死锁。
验证锁顺序一致性需要对程序中锁的行为进行整体分析,单独观察每一个锁的代码路径是不充分的,因为leftRight方法和rightLeft方法获得锁的方式都是合法的,只是它们彼此不能相互协调而已。
在实际的生产代码中,很少会出现像上面例子代码中这么明显的锁顺序死锁代码,通常会发生如下的隐式锁顺序死锁问题:
下面的转账例子演示一个动态的锁顺序死锁,代码如下:
public void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount)
throws InsufficientFundsException{
synchronized(fr