嵌套管程锁死

原文链接    作者:Jakob Jenkov

译者:余绍亮    校对:丁一

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

线程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实现:

查看源代码

打印帮助

01 //lock implementation with nested monitor lockout problem
02 public class Lock{
03     protected MonitorObject monitorObject = new MonitorObject();
04     protected boolean isLocked = false;
05  
06     public void lock() throws InterruptedException{
07         synchronized(this){
08             while(isLocked){
09                 synchronized(this.monitorObject){
10                     this.monitorObject.wait();
11                 }
12             }
13             isLocked = true;
14         }
15     }
16  
17     public void unlock(){
18         synchronized(this){
19             this.isLocked = false;
20             synchronized(this.monitorObject){
21                 this.monitorObject.notify();
22             }
23         }
24     }
25 }

可以看到,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(),这样就可以每次唤醒一个线程。

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

查看源代码

打印帮助

01 //Fair Lock implementation with nested monitor lockout problem
02 public class FairLock {
03     private boolean isLocked = false;
04     private Thread lockingThread = null;
05     private List waitingThreads =
06         new ArrayList();
07  
08     public void lock() throws InterruptedException{
09         QueueObject queueObject = new QueueObject();
10  
11         synchronized(this){
12             waitingThreads.add(queueObject);
13  
14             while(isLocked ||
15                 waitingThreads.get(0) != queueObject){
16  
17                 synchronized(queueObject){
18                     try{
19                         queueObject.wait();
20                     }catch(InterruptedException e){
21                         waitingThreads.remove(queueObject);
22                         throw e;
23                     }
24                 }
25             }
26             waitingThreads.remove(queueObject);
27             isLocked = true;
28             lockingThread = Thread.currentThread();
29         }
30     }
31  
32     public synchronized void unlock(){
33         if(this.lockingThread != Thread.currentThread()){
34             throw new IllegalMonitorStateException(
35                 "Calling thread has not locked this lock");
36         }
37         isLocked = false;
38         lockingThread = null;
39         if(waitingThreads.size() > 0){
40             QueueObject queueObject = waitingThread.get(0);
41             synchronized(queueObject){
42                 queueObject.notify();
43             }
44         }
45     }
46 }

查看源代码

打印帮助

1 public class QueueObject {}

乍看之下,嗯,很好,但是请注意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。
时间: 2024-08-31 12:35:45

嵌套管程锁死的相关文章

重入锁死

原文链接 作者:Jakob Jenkov 译者:刘晓日 校对:丁一 重入锁死与死锁和嵌套管程锁死非常相似.锁和读写锁两篇文章中都有涉及到重入锁死的问题. 当一个线程重新获取锁,读写锁或其他不可重入的同步器时,就可能发生重入锁死.可重入的意思是线程可以重复获得它已经持有的锁.Java的synchronized块是可重入的.因此下面的代码是没问题的: (译者注:这里提到的锁都是指的不可重入的锁实现,并不是Java类库中的Lock与ReadWriteLock类) 查看源代码 打印帮助 1 public

Slipped Conditions

原文链接 作者:Jakob Jenkov 译者:余绍亮 校对:丁一 所谓Slipped conditions,就是说, 从一个线程检查某一特定条件到该线程操作此条件期间,这个条件已经被其它线程改变,导致第一个线程在该条件上执行了错误的操作.这里有一个简单的例子: 01 public class Lock { 02     private boolean isLocked = true; 03   04     public void lock(){ 05       synchronized(t

Java并发性和多线程介绍目录

Java并发性和多线程介绍 多线程的优点 多线程的代价 并发编程模型 如何创建并运行java线程 竞态条件与临界区 线程安全与共享资源 线程安全及不可变性 Java内存模型 JAVA同步块 线程通信 Java ThreadLocal Thread Signaling (未翻译) 死锁 避免死锁 饥饿和公平 嵌套管程锁死 Slipped Conditions Java中的锁 Java中的读/写锁 重入锁死 信号量 阻塞队列 线程池 CAS 剖析同步器 无阻塞算法 阿姆达尔定律 文章转自 并发编程网

python线程、进程和协程详解_python

引言 解释器环境:python3.5.1 我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程.多进程的模块.一般我们在socketserver服务端代码中都会写这么一句: server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer) ThreadingTCPServer这个类是一个支持多线程和TCP协议的socketserver

iOS两个线程间嵌套发送同步消息

 先上代码,主要逻辑可看注释.最好是直接下载demo再往下看了.demo下载地址:http://download.csdn.net/detail/hursing/5159144 @implementation ViewController #define kLevelsOfNesting 5 NSString *const kParameter = @"Parameter"; NSString *const kRunLoop = @"RunLoop"; - (voi

MVP模式在携程酒店的应用和扩展

前言 酒店业务部门是携程旅行的几大业务之一,其业务逻辑复杂,业务需求变动快,经过多年的研发,已经是一个代码规模庞大的工程,如何规范代码,将代码按照其功能进行分类,将代码写到合适的地方对项目的迭代起着重要的作用. MVP模式是目前客户端比较流行的框架模式,携程在很早之前就开始探索使用该模式进行相关的业务功能开发,以提升代码的规范性和可维护性,积累了一定的经验.本文将探讨一下该模式在实际工程中的优点和缺陷,并介绍携程面对这些问题时的思考,解决方案以及在实践经验基础上对该模式的扩展模式MVCPI. 一

dsoframer.ocx打开EXCEL后,程序外部的EXCEL打开锁死现象

问题描述 dsoframer.ocx打开EXCEL后,程序外部的EXCEL打开锁死现象.请高手帮忙解决一下,万分感谢! 解决方案 解决方案二:程序内部嵌套的excel操作的同时在外部打开excel,两个同时操作不了,经常报错或卡死.解决方案三:正常,占用了同一个资源.IO有没有关闭.那个如果非要实现这样的功能,那就把资源的部分或者全部copy一份处理之后再copy回去,解决方案四:可以把要打开的excel复制到临时文件夹,修改后,在覆盖原文件.如果原文件被占用,提示用户关闭excel文件

Datalist嵌套Datalist实现显示类似说说评论的效果

问题描述 Datalist1显示say这个表的数据然后想在Datalist1中嵌套一个Datalist2用于显示对应的评论表2的sayID对应表1的id,若表2中找不到对应sayId则在对应的Datalist1中不显示Datalist2.难点在于如何达到"林夏"的说说下面只显示"黄磊"."叶伟信"的评论,"韩寒"的下面只显示"程程"的评论.(林夏的id是3,所以查询对应的表2中sayId为3的行).<

Boost.Context库简介及协程的构建

最近从各大公司的开源项目看来,基于协程的高性能服务端开发变得越来越流行了,比如我了解到的微信团队的libco.魅族的libgo.以及libcopp.传统的高性能服务端的开发大多都是基于异步框架和多线程或者多进程的模型来设计的,这种架构虽然经历了长久的考验且经验丰富,但是却有着固有的缺点: (1). 异步构架将代码逻辑强行分开,不利于人类常理的顺序思维习惯,自然也不是对开发者友好的: (2). 线程虽然相对于进程共享了大量的数据,创建和切换效率较高,算是作为内核级别轻量级别的调度单元,在X86构架