
死锁是由两个或多个线程持有的锁,等待其他线程处于死锁状态. 当多个线程同时以不同的顺序请求同一组锁时,通常会发生死锁.
例如,如果线程1锁定A,然后尝试锁定B,而线程2锁定B,然后尝试锁定A,则会发生死锁. 线程1永远不会获得B,线程2永远不会获得A,而且他们永远也不会知道发生了这种情况. 为了获得彼此的对象(A和B),它们将永远阻塞. 这种情况是一个僵局.
情况如下:

Thread 1 locks A, waits for B
Thread 2 locks B, waits for A
这是TreeNode类的示例多线程死锁的伪代码,该类调用不同实例的同步方法:
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;
}
}
如果线程1调用parent.addChild(child)方法,而另一个线程2调用child.setParent(parent)方法,则两个线程中的父对象代表相同的对象,子对象也是如此. 将会发生死锁. 以下伪代码说明了此过程:

Thread 1: parent.addChild(child); //locks parent
--> child.setParentOnly(parent);
Thread 2: child.setParent(parent); //locks child
--> parent.addChildOnly()
第一个线程1调用parent.addChild(child). 由于addChild()是同步的,因此线程1锁定了父对象,以防止其他线程访问该对象.
然后线程2调用child.setParent(parent). 由于setParent()是同步的,因此线程2锁定了子对象,以防止其他线程访问该对象.
子对象和父对象现在被两个不同的线程锁定. 下一个线程1尝试调用child.setParentOnly()方法,但是由于子对象现在已被线程2锁定,因此该调用将被阻止. 线程2还尝试调用parent.addChildOnly(),但是由于父对象现在已被线程1锁定,因此线程2也被此方法阻塞. 现在,两个线程都被阻塞,并等待获取另一个线程持有的锁.

注意: 如上所述,这两个线程需要同时调用parent.addChild(child)和child.setParent(parent)方法,以及相同的父对象和相同的子对象,因此可以死锁. 以上代码可能会在死锁发生之前运行一段时间.
这些线程需要同时获取锁. 例如,如果线程1稍稍领先于线程2,然后成功锁定了两个对象A和B,那么在尝试锁定B时线程2将被阻塞,因此不会发生死锁. 由于线程调度通常是不可预测的,因此无法准确预测何时会发生死锁,而只是可能会发生死锁.
更复杂的僵局
该死锁可能包含两个以上的线程,这使检测死锁更加困难. 以下是4个线程死锁的示例:

Thread 1 locks A, waits for B
Thread 2 locks B, waits for C
Thread 3 locks C, waits for D
Thread 4 locks D, waits for A
线程1等待线程2,线程2等待线程3,线程3等待线程4,线程4等待线程1.
更复杂的死锁方案发生在事务中. 事务可能包含多个SQL更新请求. 当在一个事务中更新一条记录时多线程死锁的伪代码,该记录将被锁定以避免其他事务的更新请求,直到第一个事务结束. 同一事务中的每个更新请求都可以锁定一些记录.
当多个事务需要同时更新某些相同记录时,可能会发生死锁,例如:
Transaction 1, request 1, locks record 1 for update
Transaction 2, request 1, locks record 2 for update
Transaction 1, request 2, tries to lock record 2 for update.
Transaction 2, request 2, tries to lock record 1 for update.
因为锁发生在不同的请求中,并且事务不可能事先知道它需要的所有锁,所以很难检测和避免事务中的死锁.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-273696-1.html
但绝不容忍侵犯主权