线程死锁
死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。
public class TreeNode {
TreeNode parent = null;
List children = new ArrayList();
public synchronized void addChild(TreeNode child){
if(!this.children.contains(child)) {
this.children.add(child);
child.setParentOnly(this);
}
}
public synchronized void addChildOnly(TreeNode child){
if(!this.children.contains(child){
this.children.add(child);
}
}
public synchronized void setParent(TreeNode parent){
this.parent = parent;
parent.addChildOnly(this);
}
public synchronized void setParentOnly(TreeNode parent){
this.parent = parent;
}
}
// Thread 1: parent.addChild(child); //locks parent
--> child.setParentOnly(parent);
// Thread 2: child.setParent(parent); //locks child
--> parent.addChildOnly()
如果child和parent是同一个对象,而且两个线程同时执行,两个线程同时获得锁,那么此时就会发生死锁。没有办法预测什么时候死锁会发生,仅仅是可能会发生。
数据库死锁
当在一个事务中更新一条记录,这条记录就会被锁住避免其他事务的更新请求,直到第一个事务结束。同一个事务中每一个更新请求都可能会锁住一些记录。当多个事务同时需要对一些相同的记录做更新操作时,就很有可能发生死锁。
死锁的预防
锁的顺序
死锁在多线程以不同的顺序请求相同的锁的时候发生。如果保证所有线程加锁的顺序相同,那么久不会发生死锁。
public class DeadLockFixed {
public void method1() {
synchronized(Integer.class) {
System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}
public void method2() {
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}
}
锁超时
另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行。
如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,这种现象可能不会发生,但是如果是10个或20个线程情况就不同了。
死锁检测
使用一个数据结构维护线程所获取的锁。当锁请求失败的时候,线程遍历锁关系图查看是否有死锁发生。
解决死锁方法:
- 释放所有锁,回退,等待一定时间后重试
- 给这些线程设置优先级,让一个或者多个线程进行回退