`
85977328
  • 浏览: 1898906 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java并发(十三)嵌套管程锁死

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

引用
线程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();
            }
        }
    }
}
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。
分享到:
评论

相关推荐

    Java并发之嵌套管程锁死详解

    Java并发之嵌套管程锁死详解 Java并发编程中,嵌套管程锁死是一个经常出现的问题,严重影响着系统的性能和稳定性。嵌套管程锁死是由多个线程竞争资源导致的死锁现象。 嵌套管程锁死的发生: 嵌套管程锁死的发生是...

    JAVA管程解决哲学家问题

    在Java编程领域,管程(Monitor)是一种用于解决并发控制问题的机制,它提供了一种在多线程环境中同步和互斥访问共享资源的方式。哲学家问题(Dining Philosophers Problem)是计算机科学中的一个经典问题,用于演示...

    大厂学院高阶班java并发编程面试视频资料(6.95G)

    大厂学院高阶班java并发编程面试视频资料(6.95G) 〖课程介绍〗: 大厂学院高阶班java并发编程面试视频资料(6.95G) 〖课程目录〗: 01_前言.mp4 44.96M 02_线程的start方法.mp4 68.78M 03_进程线程管程.mp4 54.58M ...

    Java并发编程实践PDF电子书

    这一章主要讲述Java并发集合,如ConcurrentHashMap、ConcurrentLinkedQueue、BlockingQueue等,它们在并发环境下提供了线程安全的访问和操作,避免了传统的同步锁带来的性能瓶颈。 第六章:同步容器与并发工具 本章...

    JAVA管程解决哲学家就餐问题

    **JAVA管程解决哲学家...总之,使用Java管程解决哲学家就餐问题展示了如何优雅地处理并发编程中的同步问题。通过理解管程的工作原理和应用,开发者可以更好地设计和实现多线程系统,避免因资源竞争导致的错误和死锁。

    Java并发编程解析 | 解析AQS基础同步器的设计与实现

    "Java并发编程解析 | 解析AQS基础同步器的设计与实现" 在Java领域中,解决并发编程问题的关键是解决同步和互斥的问题。同步是指线程之间的通信和协作,互斥是指同一时刻只能允许一个线程访问共享资源。Java领域中有...

    并发编程的艺术

    8. **并发编程模式**:包括生产者-消费者模式、读者-写者模式、双检锁/双重校验锁(DCL)、管程等,这些模式在解决并发问题时有着广泛应用。 9. **线程安全**:理解什么是线程安全的类和方法,以及如何编写线程安全...

    java多线程与并发1

    3. 管程:Java中的Semaphore、CountDownLatch、CyclicBarrier等同步工具,用于控制并发线程的数量或协调线程执行。 三、并发容器 1. 原生容器:ArrayList、LinkedList等不保证线程安全,而Vector、Stack则是线程...

    Java 多线程共享模型之管程(下).doc

    Java 多线程编程中,管程是一种用于实现线程间同步和协作的重要机制,它主要依赖于Java内置的锁机制和条件变量。在Java中,管程主要通过`wait()`, `notify()`, `notifyAll()`以及`ReentrantLock`等工具来实现。下面...

    操作系统课程设计 Nachos 管程的实现

    管程可以很好地解决这个问题,通过内置的锁机制和条件变量,实现线程间的协作与同步。 在Nachos的实现中,首先,我们需要创建一个管程类,它通常包含一组共享变量(如缓冲区的状态、数据的数量等)和一系列的同步...

    Java并发编程-并发编程知识点总结.docx

    - **管程锁定规则**:对于同一个锁,一个解锁操作必须在另一个锁定操作之前发生。 - **volatile变量规则**:前一个对`volatile`的写操作在后一个`volatile`的读操作之前。 - **线程启动规则**:线程内的所有操作必须...

    利用管程概念求解哲学家进餐问题1

    综上所述,利用管程概念解决哲学家进餐问题,不仅提高了程序的模块化和可读性,还通过Java的高级并发对象提供了精确的线程同步和唤醒机制,有效地防止了死锁的发生。这种方法对于理解和处理并发控制问题具有重要的...

    LEC11 并发程序设计(含PV操作 管程 消息)1

    《并发程序设计(含PV操作 管程 消息)》 并发程序设计是操作系统中的核心概念,它涉及到多进程或线程的同步与协作。本讲主要讲解了并发进程的基本理论,以及如何通过互斥和通信机制解决并发执行中的问题。 11.1 ...

    Java 多线程共享模型之管程(上).doc

    Java 多线程共享模型之管程(上).doc

    6.11 霍尔管程的例1

    在这个场景下,我们关注的是两个经典的问题:哲学家问题和读者写者问题,这两个问题都是并发控制中的典型示例,它们都通过使用管程(Monitor)来解决。 首先,让我们看哲学家问题。这个问题描述了五个哲学家围坐在...

    java高并发相关知识点.docx

    Java高并发编程是Java开发中的重要领域,尤其在大规模分布式系统和互联网应用中,对系统的高并发处理能力有着极高的要求。以下将详细介绍这些关键知识点: 1. **线程** - **继承Thread类**:创建线程的一种方式是...

Global site tag (gtag.js) - Google Analytics