浅谈死锁原理

简介: 死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。


死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。

image.png

例如,如果线程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类的例子,它调用了不同实例的synchronized方法:

publicclassTreeNode {

   TreeNodeparent   =null;  

   Listchildren=newArrayList();

   publicsynchronizedvoidaddChild(TreeNode child){

       if(!this.children.contains(child)) {

           this.children.add(child);

           child.setParentOnly(this);

       }

   }

   publicsynchronizedvoidaddChildOnly(TreeNode child){

       if(!this.children.contains(child){

           this.children.add(child);

       }

   }

   publicsynchronizedvoidsetParent(TreeNode parent){

       this.parent = parent;

       parent.addChildOnly(this);

   }

   publicsynchronizedvoidsetParentOnly(TreeNode parent){

       this.parent = parent;

   }

}

如果线程1调用parent.addChild(child)方法的同时有另外一个线程2调用child.setParent(parent)方法,两个线程中的parent表示的是同一个对象,child亦然,此时就会发生死锁。下面的伪代码说明了这个过程:

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会对parent对象加锁以不让其它线程访问该对象。

然后线程2调用child.setParent(parent)。因为setParent()是同步的,所以线程2会对child对象加锁以不让其它线程访问该对象。


现在child和parent对象被两个不同的线程锁住了。接下来线程1尝试调用child.setParentOnly()方法,但是由于child对象现在被线程2锁住的,所以该调用会被阻塞。线程2也尝试调用parent.addChildOnly(),但是由于parent对象现在被线程1锁住,导致线程2也阻塞在该方法处。现在两个线程都被阻塞并等待着获取另外一个线程所持有的锁。


注意:像上文描述的,这两个线程需要同时调用parent.addChild(child)和child.setParent(parent)方法,并且是同一个parent对象和同一个child对象,才有可能发生死锁。上面的代码可能运行一段时间才会出现死锁。


这些线程需要同时获得锁。举个例子,如果线程1稍微领先线程2,然后成功地锁住了A和B两个对象,那么线程2就会在尝试对B加锁的时候被阻塞,这样死锁就不会发生。因为线程调度通常是不可预测的,因此没有一个办法可以准确预测什么时候死锁会发生,仅仅是可能会发生。


更复杂的死锁

死锁可能不止包含2个线程,这让检测死锁变得更加困难。下面是4个线程发生死锁的例子:

Thread1  locksA,waitsforB

Thread2  locksB,waitsforC

Thread3  locksC,waitsforD

Thread4  locksD,waitsforA

线程1等待线程2,线程2等待线程3,线程3等待线程4,线程4等待线程1。


数据库的死锁

更加复杂的死锁场景发生在数据库事务中。一个数据库事务可能由多条SQL更新请求组成。当在一个事务中更新一条记录,这条记录就会被锁住避免其他事务的更新请求,直到第一个事务结束。同一个事务中每一个更新请求都可能会锁住一些记录。

当多个事务同时需要对一些相同的记录做更新操作时,就很有可能发生死锁,例如:

Transaction1, request 1, locks record1forupdate

Transaction2, request 1, locks record2forupdate

Transaction1, request 2, tries tolockrecord2forupdate.

Transaction2, request 2, tries tolockrecord1forupdate.

因为锁发生在不同的请求中,并且对于一个事务来说不可能提前知道所有它需要的锁,因此很难检测和避免数据库事务中的死锁。

相关文章
|
9月前
|
Java
Java并发编程中的死锁问题及解决方法
【2月更文挑战第11天】 在Java并发编程中,死锁是一个常见但又非常棘手的问题。本文将深入探讨死锁的概念、产生原因以及常见的解决方法,帮助读者更好地理解并发编程中的挑战,并提供实用的解决方案。
112 6
|
8月前
|
Python
Python多线程中递归锁如何解决死锁问题的详细阐述
Python多线程中递归锁如何解决死锁问题的详细阐述
|
9月前
|
安全
什么是死锁?互斥锁进入死锁怎么解决?
什么是死锁?互斥锁进入死锁怎么解决?
|
9月前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
120 1
|
9月前
|
安全 算法 关系型数据库
线程安全--深入探究线程等待机制和死锁问题
线程安全--深入探究线程等待机制和死锁问题
298 1
|
9月前
|
算法 安全
解决死锁的方法
解决死锁的方法
|
算法 安全 Java
死锁的原理
之前在学校学习过程中,很少写多进程的代码,虽然操作系统中学过死锁相关的内容,但考试过后也基本就忘记了,后来自己也遇到过有些多进程死锁的情况,再加上看了有些资料,对死锁才算是有了有些深入的理解。
108 0
|
安全
什么是死锁?(把死锁给大家讲明白,知道是什么,为什么用,怎么用)
什么是死锁?(把死锁给大家讲明白,知道是什么,为什么用,怎么用)
106 0
什么是死锁?(把死锁给大家讲明白,知道是什么,为什么用,怎么用)
死锁概念
本章讲解什么是死锁以及如何解决
96 0
|
存储 关系型数据库 MySQL
面试官:解释下什么是死锁?为什么会发生死锁?怎么避免死锁?
开局先来个段子: 面试官: 解释下什么是死锁? 应聘者: 你录用我,我就告诉你 面试官: 你告诉我,我就录用你 应聘者: 你录用我,我就告诉你 面试官: 滚!

相关实验场景

更多