Skip to content

JVM监视器锁是什么

JVM监视器锁是Java虚拟机内部用来实现线程同步的一种机制。它是synchronized关键字背后的核心技术,确保在同一时刻,只有一个线程可以执行被synchronized修饰的代码块或方法,从而保证了线程安全。

你可以把监视器想象成一个与每个Java对象关联的“特殊房间”。这个房间有一个唯一的锁,并且有特定的规则来管理想要进入的线程。

监视器锁的核心组成部分

每个Java对象在JVM层面都与一个监视器(Monitor)相关联。这个监视器主要由以下三个部分组成:

  1. 一个特殊的所有者(Owner)字段: 这个字段用于记录当前哪个线程持有了该监视器锁。在任意时刻,这个字段最多只能指向一个线程,或者为null(表示锁未被任何线程持有)。
  2. 一个入口集(Entry Set): 这是一个等待队列,用于存放所有尝试获取但尚未获得该监视器锁的阻塞线程。当一个线程试图进入一个已被占用的synchronized代码块时,它就会被放入这个入口集中等待。
  3. 一个等待集(Wait Set): 这是另一个等待队列,用于存放调用了该对象wait()方法的线程。这些线程是主动释放了监视器锁并进入等待状态,等待被其他线程通过notify()notifyAll()唤醒。

监视器锁的工作流程

当一个线程遇到synchronized关键字时,其工作流程如下:

  1. 尝试获取锁:

    • 线程会尝试获取与该对象关联的监视器锁。
    • 它会检查监视器的Owner字段是否为null
    • 如果为null,表示锁是空闲的,该线程将成功获取锁,并将监视器的Owner字段设置为自己。然后,它可以进入synchronized代码块执行。
  2. 获取锁失败(锁已被占用):

    • 如果线程发现监视器的Owner字段指向的是另一个线程,表示锁已被占用。
    • 此时,该线程的执行会受阻,它将被放入该监视器的入口集(Entry Set)中,并进入阻塞状态(BLOCKED)。
  3. 释放锁:

    • 当持有锁的线程执行完毕synchronized代码块或方法后(无论是正常退出还是因异常退出),它会自动释放这个监视器锁。
    • 释放锁的操作包括将监视器的Owner字段重新设置为null
  4. 唤醒等待的线程:

    • 锁被释放后,JVM会从该监视器的入口集(Entry Set)中唤醒一个或多个等待的线程(具体唤醒哪个线程是不确定的,取决于JVM的调度策略)。
    • 被唤醒的线程会再次尝试获取锁,这个过程会产生竞争,最终只有一个线程能成功获取锁并继续执行。

监视器锁与 wait() / notify() / notifyAll() 的交互

监视器锁不仅实现了互斥(Mutual Exclusion),还支持线程间的协作,这就要依靠wait()notify()notifyAll()这三个方法。

  • wait():

    1. 一个线程必须首先持有对象的监视器锁,才能调用该对象的wait()方法。
    2. 调用wait()后,该线程会立即释放它持有的监视器锁。
    3. 然后,该线程会被放入该监视器的等待集(Wait Set)中,并进入等待状态(WAITING 或 TIMED_WAITING)。
    4. 它会一直在此等待,直到被其他线程唤醒。
  • notify() / notifyAll():

    1. 一个线程也必须首先持有对象的监视器锁,才能调用该对象的notify()notifyAll()方法。
    2. notify()会从等待集(Wait Set)中随机选择一个线程,并将其唤醒。被唤醒的线程会被移动到入口集(Entry Set)中,准备重新竞争锁。
    3. notifyAll()则会唤醒等待集(Wait Set)中所有的线程,并将它们全部移动到入口集(Entry Set)中,让它们一起去竞争锁。
    4. 非常重要的一点:调用notify()notifyAll()的线程并不会立即释放锁。它必须等到自己执行完synchronized代码块后,才会释放锁。这时,那些被唤醒并移动到入口集的线程才有机会去获取锁。