死锁

时间:2020-01-09 10:35:51  来源:igfitidea点击:

线程死锁

死锁是指两个或者两个以上的线程被阻塞,等待获得死锁中其他一些线程持有的锁。当多个线程同时需要相同的锁,但是以不同的顺序获得它们时,就会发生死锁。

例如,如果线程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将已经被阻塞。这样就不会发生死锁。由于线程调度通常是不可预测的,因此无法预测何时发生死锁。只有它\ *可以\ *发生。

更加复杂的僵局

死锁还可以包括两个以上的线程。这使得更难检测。这是四个线程陷入僵局的示例:

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.

由于锁是在不同的请求中获得的,并且并非提前知道给定事务所需的所有锁,因此很难检测或者防止数据库事务中的死锁。