以下内容转自http://ifeve.com/nested-monitor-lockout/:

嵌套管程锁死类似于死锁, 下面是一个嵌套管程锁死的场景:

线程1获得A对象的锁。
线程1获得对象B的锁(同时持有对象A的锁)。
线程1决定等待另一个线程的信号再继续。
线程1调用B.wait(),从而释放了B对象上的锁,但仍然持有对象A的锁。线程2需要同时持有对象A和对象B的锁,才能向线程1发信号。
线程2无法获得对象A上的锁,因为对象A上的锁当前正被线程1持有。
线程2一直被阻塞,等待线程1释放对象A上的锁。线程1一直阻塞,等待线程2的信号,因此,不会释放对象A上的锁, 而线程2需要对象A上的锁才能给线程1发信号……

你可以能会说,这是个空想的场景,好吧,让我们来看看下面这个比较挫的Lock实现:

//lock implementation with nested monitor lockout problem
public class Lock{protected MonitorObject monitorObject = new MonitorObject();protected boolean isLocked = false;public void lock() throws InterruptedException{synchronized(this){while(isLocked){synchronized(this.monitorObject){this.monitorObject.wait();}}isLocked = true;}}public void unlock(){synchronized(this){this.isLocked = false;synchronized(this.monitorObject){this.monitorObject.notify();}}}
}

可以看到,lock()方法首先在”this”上同步,然后在monitorObject上同步。如果isLocked等于false,因为线程不会继续调用monitorObject.wait(),那么一切都没有问题 。但是如果isLocked等于true,调用lock()方法的线程会在monitorObject.wait()上阻塞。

这里的问题在于,调用monitorObject.wait()方法只释放了monitorObject上的管程对象,而与”this“关联的管程对象并没有释放。换句话说,这个刚被阻塞的线程仍然持有”this”上的锁。

(校对注:如果一个线程持有这种Lock的时候另一个线程执行了lock操作)当一个已经持有这种Lock的线程想调用unlock(),就会在unlock()方法进入synchronized(this)块时阻塞。这会一直阻塞到在lock()方法中等待的线程离开synchronized(this)块。但是,在unlock中isLocked变为false,monitorObject.notify()被执行之后,lock()中等待的线程才会离开synchronized(this)块。)

简而言之,在lock方法中等待的线程需要其它线程成功调用unlock方法来退出lock方法,但是,在lock()方法离开外层同步块之前,没有线程能成功执行unlock()。

结果就是,任何调用lock方法或unlock方法的线程都会一直阻塞。这就是嵌套管程锁死。

一个更现实的例子

你可能会说,这么挫的实现方式我怎么可能会做呢?你或许不会在里层的管程对象上调用wait或notify方法,但完全有可能会在外层的this上调。

有很多类似上面例子的情况。例如,如果你准备实现一个公平锁。你可能希望每个线程在它们各自的QueueObject上调用wait(),这样就可以每次唤醒一个线程。

下面是一个比较挫的公平锁实现方式:

//Fair Lock implementation with nested monitor lockout problem
public class FairLock {private boolean isLocked = false;private Thread lockingThread = null;private List waitingThreads = new ArrayList();public void lock() throws InterruptedException{QueueObject queueObject = new QueueObject();synchronized(this){waitingThreads.add(queueObject);while(isLocked || waitingThreads.get(0) != queueObject){synchronized(queueObject){try{queueObject.wait();}catch(InterruptedException e){waitingThreads.remove(queueObject);throw e;}}}waitingThreads.remove(queueObject);isLocked = true;lockingThread = Thread.currentThread();}}public synchronized void unlock(){if(this.lockingThread != Thread.currentThread()){throw new IllegalMonitorStateException("Calling thread has not locked this lock");}isLocked = false;lockingThread = null;if(waitingThreads.size() > 0){QueueObject queueObject = waitingThread.get(0);synchronized(queueObject){queueObject.notify();}}}
}

乍看之下,嗯,很好,但是请注意lock方法是怎么调用queueObject.wait()的,在方法内部有两个synchronized块,一个锁定this,一个嵌在上一个synchronized块内部,它锁定的是局部变量queueObject。

当一个线程调用queueObject.wait()方法的时候,它仅仅释放的是在queueObject对象实例的锁,并没有释放”this”上面的锁。

现在我们还有一个地方需要特别注意, unlock方法被声明成了synchronized,这就相当于一个synchronized(this)块。这就意味着,如果一个线程在lock()中等待,该线程将持有与this关联的管程对象。所有调用unlock()的线程将会一直保持阻塞,等待着前面那个已经获得this锁的线程释放this锁,但这永远也发生不了,因为只有某个线程成功地给lock()中等待的线程发送了信号,this上的锁才会释放,但只有执行unlock()方法才会发送这个信号。

因此,上面的公平锁的实现会导致嵌套管程锁死。更好的公平锁实现方式可以参考Starvation and Fairness。

嵌套管程锁死 VS 死锁

嵌套管程锁死与死锁很像:都是线程最后被一直阻塞着互相等待。

但是两者又不完全相同。在死锁中我们已经对死锁有了个大概的解释,死锁通常是因为两个线程获取锁的顺序不一致造成的,线程1锁住A,等待获取B,线程2已经获取了B,再等待获取A。如死锁避免中所说的,死锁可以通过总是以相同的顺序获取锁来避免。

但是发生嵌套管程锁死时锁获取的顺序是一致的。线程1获得A和B,然后释放B,等待线程2的信号。线程2需要同时获得A和B,才能向线程1发送信号。所以,一个线程在等待唤醒,另一个线程在等待想要的锁被释放。

不同点归纳如下:

死锁中,二个线程都在等待对方释放锁。嵌套管程锁死中,线程1持有锁A,同时等待从线程2发来的信号,线程2需要锁A来发信号给线程1。

==>如有问题,请联系我:easonjim#163.com,或者下方发表评论。<==

19、Java并发性和多线程-嵌套管程锁死相关推荐

  1. 【转】JAVA 并发性和多线程 -- 读感 (二 线程间通讯,共享内存的机制)

    原文地址:https://www.cnblogs.com/edenpans/p/6020113.html 参考文章:http://ifeve.com/java-concurrency-thread-d ...

  2. 并发基础篇(一): Java 并发性和多线程

    说在前面 介绍文章之前,先给出一个多线程的思维导图, 后续的文章就根据思维导图来一步一步的分析java多线程的知识. 一.介绍 在过去单 CPU 时代,单任务在一个时间点只能执行单一程序.之后发展到多 ...

  3. Java并发性和多线程介绍

    作者:Jakob Jenkov 译者:Simon-SZ  校对:方腾飞 http://tutorials.jenkov.com/java-concurrency/index.html 在过去单CPU时 ...

  4. 5、Java并发性和多线程-相同线程

    以下内容转自http://tutorials.jenkov.com/java-concurrency/same-threading.html(使用谷歌翻译): 相同线程(同一线程)是一种并发模型,其中 ...

  5. 17、Java并发性和多线程-避免死锁

    以下内容转自http://ifeve.com/deadlock-prevention/: 在有些情况下死锁是可以避免的.本文将展示三种用于避免死锁的技术: 加锁顺序 当多个线程需要相同的一些锁,但是按 ...

  6. 18、Java并发性和多线程-饥饿与公平

    以下内容转自http://ifeve.com/starvation-and-fairness/: 如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为"饥饿& ...

  7. java开源线程池_线程池 - Java 并发性和多线程 - UDN开源文档

    线程池 线程池(Thread Pool)对于限制应用程序中同一时刻运行的线程数很有用.因为每启动一个新线程都会有相应的性能开销,每个线程都需要给栈分配一些内存等等. 我们可以把并发执行的任务传递给一个 ...

  8. 21、Java并发性和多线程-Java中的锁

    以下内容转自http://ifeve.com/locks/: 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂.因为锁(以及其它更高级的 ...

  9. 【Java并发性和多线程】Java中的锁

    2019独角兽企业重金招聘Python工程师标准>>> 本文为转载学习 原文链接:http://ifeve.com/locks/ 锁像synchronized同步块一样,是一种线程同 ...

最新文章

  1. linux docker安装mysql_Linux-docker安装mysql
  2. 【黑马程序员 C++教程从0到1入门编程】【笔记3】C++核心编程(内存分区模型、引用、函数提高)
  3. 北斗导航 | 读取ground truth data(python源代码)
  4. 投巧解决JavaScript split方法出现空字符的问题
  5. html中高与行高的区别,深入了解css的行高Line Height属性
  6. Oracle 索引概述
  7. APT/APT-GET常用信息
  8. 对付ring0 inline hook
  9. animation 循环_(五)实践出真知——Python 之定时器、线程、动画制作Animation类...
  10. 查询 加载时间过长添加提示信息
  11. VM Player虚拟机的固定IP方法
  12. linux wipe命令,如何使用wipefs命令擦除磁盘上的签名
  13. English_do
  14. VB.NET 通过vbs发送微信消息
  15. 服务器搭建邮件自动回复,爆笑的邮件自动回复内容,邮件自动回复心理
  16. 排队队---排列组合之插空法与捆绑法
  17. 0.1.2 arduinodroid安卓手机版开发工具
  18. 谷歌正式开放「Bard」试用,很遗憾。。
  19. 用计算机打元宵节快乐,元宵节快乐祝福短语
  20. 王道 Online Judge

热门文章

  1. java final 变量 回收_在Java中将final用于变量会改善垃圾回收吗?
  2. python中引入sql的优点_SQL Server 2017中的Python:增强的数据库内机器学习
  3. python语句分为复合语句_复合语句if条件的Python求值
  4. python多个判断条件_Python基础介绍 | 条件判断Conditionals
  5. Matlab求解混沌系统最大李雅普诺夫指数
  6. matlab datetime时间处理、时间转换
  7. linux网络编程IPv6socket,简单的IPv6 UDP/TCP socket编程 -- 两台Linux实现简单的ipv6通信...
  8. linux目录挂载到内存,Linux中内存挂载到目录下
  9. 获取视频的每一帧,并保存为.jpg图片
  10. Android 学习指南(2017版)