文档章节

【14】死锁

秋雨霏霏
 秋雨霏霏
发布于 2017/09/09 17:12
字数 1356
阅读 31
收藏 1

线程死锁

当两个或者多个线程互相阻塞等待对方持有的锁的时候,就会发生死锁。 也就是说,线程在申请某个锁的同时,持有者其他线程需要的锁,这通常是由于加锁的顺序不一致而导致的。

举例来说,如果线程1已经持有着锁A,然后再申请锁B;同时,线程2已经持有了锁B,然后申请锁A,这样就出现了死锁。 线程1永远得不到锁B,而线程2也永远得不到锁A。 关键是,两个线程对此还一无所知。 两个线程还是会继续持有着已获得的锁并一直等着另一个锁。这就是死锁。

两个线程的逻辑,如下所示:

Thread 1  locks A, waits for B
Thread 2  locks B, waits for A

下面这个例子,展示了一个TreeNode类,这个类上的方法都是synchronized的。

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;
  }
}

由于内部还定义了一个parent属性,在synchronized方法中还对parent的方法进行了调用。由于都是TreeNode类型,synchronized实际是对实例的监控器锁的操作。这就会导致当前对象的synchronized方法,调用另一个对象的synchronized方法。 这实际上就成了,在持有当前实例的监控器锁的同时,去申请另一个实例的监控器锁。而这种情况,就可能会导致死锁的发生。

想象一下,如果线程1在parent实例上调用了parent.addChild(child)方法,同时,线程2在parent的子节点child上调用了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)方法,因为方法是synchronized,所以,线程1也就持有了parent上的锁。

然后,线程2调用child.setParent(parent)方法,同样是一个synchronized方法,所以,线程2也就获得了child对象上的锁。

这样,parent和child上的锁,就分别被线程1和线程2获得。但是接下来,线程1就会去调用child.setParentOnly(),然而child上的锁已经被线程2获得了,所以线程1只好阻塞等待。 同样的,线程2接下来会去调用parent.addChildOnly(),而parent上锁正被线程1所持有,所以线程2也只好阻塞等待。 这样就导致了,线程1在等待线程2释放child上的锁;线程2在等待线程1释放parent上的锁。

注意:只有在线程1和线程2同时调用,才会死锁。例如,如果线程1稍稍领先于线程2,那就会使得线程1会顺利的获得锁A和B,而线程2会阻塞等待B。这样就不会死锁了。 而由于线程的调度是不可预知的,所以无法准确的说死锁会什么时候发生。我们只能说,这样的逻辑会导致死锁。

更复杂的死锁

死锁不仅仅是只有两个线程才会发生。实际中,情况会更为复杂,这也是为啥说死锁比较难排查的原因。 比如来看看下面这个情况:

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

这就像一个有向图中的圈。如果把线程比作图中顶点,等待另一个线程的锁比作一条边,如果这个有向图中形成了圈,那就意味着死锁。

数据库死锁

数据库中的事务,死锁会变得更为复杂。 数据库事务可能会包含多条SQL update 请求。 当事务中的某条记录被更新时,这条记录可能正被其他事务加锁。这样当前事务就需要等待加锁事务的完成。

而一个事务中每一个更新请求都可能需要对某些记录进行加锁处理。 这样同时执行的多个事务就可能需要对相同的记录进行更新,这样就会产生死锁风险。 例如:

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.

由于加锁动作发生于不同的请求,而且当前事务无法预知全部的锁信息,所以,数据库事务发生死锁变得更难发现。


补充几点

可以对死锁的情况,做出一些整理:

  • 锁顺序死锁(lock-ordering deadlock)
    • 两个线程试图通过不同的顺序获得多个相同的锁
    • 注意外部参数引起的顺序变化
    • 致命的拥抱(deadly embrace)
      • 加锁顺序导致持有着对方的锁,并等待对方释放自己需要的锁
    • 环路的依赖关系
    • 持有锁,然后调用外部方法
      • 外部方法不知道会不会在其他对象上加锁
  • 资源死锁(resource deadlock)
    • 资源阻塞
    • 线程饥饿死锁(thread-starvation deadlock)

© 著作权归作者所有

共有 人打赏支持
秋雨霏霏
粉丝 150
博文 94
码字总数 168411
作品 0
杭州
CTO(技术副总裁)
私信 提问
程序员的自我修养——操作系统篇

目录: 1. 进程的有哪几种状态,状态转换图,及导致转换的事件。 2. 进程与线程的区别。 3. 进程通信的几种方式。 4. 线程同步几种方式。 5. 线程的实现方式. (用户线程与内核线程的区别) 6...

马浩
2014/06/30
0
0
高并发Java(5):JDK并发包1

在高并发Java(2):多线程基础中,我们已经初步提到了基本的线程同步操作。这次要提到的是在并发包中的同步控制工具。 1. 各种同步控制工具的使用 1.1 ReentrantLock ReentrantLock感觉上是...

卯金刀GG
2017/11/02
0
0
精选30道Java多线程面试题

1、线程和进程的区别 2、实现线程有哪几种方式? 3、线程有哪几种状态?它们之间如何流转的? 4、线程中的start()和run()方法有什么区别? 5、怎么终止一个线程?如何优雅地终止线程? 6、T...

java技术栈
2017/08/13
0
0
Java多线程顺序有序执行

一道面试题、同时开启三个线程A/B/C,A线程打印1,2,3,4,5;B线程打印6,7,8,9,10;C线程打印11,12,13,14,15;然后A线程继续打印16,17,18,19,20。。。。最后直到线程C打印71,72,73,74,75程序结束。...

ileler
2015/07/07
5.8K
6
JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制 JAVA之旅,一路有你,加油! 一.静态同步函数的锁是c...

刘桂林
2016/06/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spak—— sparkCore源码解析之RangePartitioner源码

   分区过程概览 RangePartitioner分区执行原理: 计算总体的数据抽样大小sampleSize,计算规则是:至少每个分区抽取20个数据或者最多1M的数据量。 根据sampleSize和分区数量计算每个分区的...

freeli
22分钟前
1
0
从内部自用到对外服务,配置管理的演进和设计优化实践

本文整理自阿里巴巴中间件技术专家彦林在中国开源年会上的分享,通过此文,您将了解到: 微服务给配置管理所带来的变化 配置管理演进过程中的设计思考 配置管理开源后的新探索 配置中心控制台...

阿里云官方博客
23分钟前
1
0
MySQL用户管理,常用MySQL语句、MySQL数据库备份恢复

12月6日任务 13.4 mysql用户管理 13.5 常用sql语句 13.6 mysql数据库备份恢复 13.4 mysql用户管理 grant all on *.* to 'user1' identified by 'passwd'; grant SELECT,UPDATE,INSERT on db......

zgxlinux
24分钟前
3
0
Spring异常之Druid – unregister mbean error

Spring异常之Druid – unregister mbean error 2017年04月19日 12:13:42 Dr.Zhu 阅读数:6688 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zt_fucker/arti...

linjin200
30分钟前
1
0
微信小程序webview问题

今天在改小程序的时候在使用webview的时候切换webview的地址行为,出现了诡异的情况。 默认querystring里会有多个?符号,使用的时候被微信给截取了,导致程序找不到改页面。 而且querystri...

钟元OSS
33分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部