一、Monitor 核心概念

1.1 定义与核心作用

  • 核心定义:Monitor(中文译为 “管程”)是一种同步机制,本质是对象的同步工具,用于保证同一时间仅有一个线程能访问共享资源的临界区,解决多线程并发下的竞态条件问题。
  • 核心目标
    1. 互斥性:限制临界区同一时间只能被一个线程执行(解决指令交错)。
    2. 协作性:支持线程间通过等待 / 通知机制(wait/notify)实现高效协作(如生产者 - 消费者模型)。
  • 生活类比:Monitor 如同一个带锁的房间,临界区代码是房间内的资源,线程需先获取锁(钥匙)才能进入,房间内同一时间只能有一个人(线程),线程完成后释放锁,其他等待线程再竞争进入。

1.2 底层原理(Java 中的 Monitor 实现)

  • 对象与 Monitor 的关联:Java 中每个对象都天生关联一个 Monitor(即 “对象锁” 的本质),通过对象头(Object Header)中的 Mark Word 存储 Monitor 的关联信息(如锁状态、持有锁的线程 ID)。
  • Monitor 的所属关系
    • 普通对象:创建对象时,JVM 自动为其分配对应的 Monitor(懒加载,首次竞争锁时初始化)。
    • 类对象(Class):静态同步方法的锁本质是类对象的 Monitor。
  • 底层依赖:Java 的 Monitor 基于操作系统的互斥量(Mutex)和条件变量(Condition Variable)实现,但通过 JVM 优化(如偏向锁、轻量级锁)避免直接调用操作系统内核态函数,减少性能开销。

1.3 Monitor 关键组成部分

组成部分 作用
Entry Set(入口队列) 等待获取锁的线程队列,线程竞争锁失败时进入此队列(阻塞状态:BLOCKED)。
Owner(锁持有者) 当前持有 Monitor 锁的线程,同一时间仅能有一个线程成为 Owner。
Wait Set(等待队列) 调用 wait() 后释放锁的线程队列(阻塞状态:WAITING/TIMED_WAITING)。
互斥锁(Mutex) 保证临界区互斥访问的核心组件,控制线程进入临界区的权限。
条件变量(Condition) 支持线程间协作,提供 wait()/notify()/notifyAll() 方法实现等待 - 通知。

二、Monitor 工作流程(线程竞争与协作)

图示:

2.1 锁竞争流程(Entry Set → Owner)

  1. 线程尝试进入临界区(执行 synchronized 代码块 / 方法),首先尝试获取 Monitor 锁。
  2. 若 Monitor 无持有者(锁空闲):当前线程直接成为 Owner,标记 Mark Word 为 “锁定状态”,执行临界区代码。
  3. 若 Monitor 已被其他线程持有(锁占用):当前线程进入 Entry Set,状态变为 BLOCKED,等待锁释放。
  4. 当 Owner 线程释放锁(正常退出临界区 / 异常 / 调用 wait()):JVM 从 Entry Set 中唤醒一个线程(非公平,默认策略),该线程重新竞争锁,成功后成为新的 Owner。

2.2 线程协作流程(Owner → Wait Set → Entry Set)

  1. Owner 线程执行过程中,若需等待某个条件满足(如生产者等待队列非空),调用 wait() 方法:
    • 释放当前持有的 Monitor 锁,状态从 RUNNABLE 变为 WAITING(或 TIMED_WAITING,若调用 wait(long))。
    • 线程进入 Wait Set,等待其他线程的通知。
  2. 其他线程(通常是 Owner 线程)完成操作后,调用 notify() 或 notifyAll() 方法:
    • notify():从 Wait Set 中随机唤醒一个线程,该线程移至 Entry Set,状态变为 BLOCKED,等待重新竞争锁。
    • notifyAll():唤醒 Wait Set 中所有线程,所有线程均移至 Entry Set,竞争锁。
  3. 被唤醒的线程竞争锁成功后,重新成为 Owner,从 wait() 方法返回,继续执行后续代码。

2.3 锁释放的三种场景

  1. 线程正常执行完临界区代码,自动释放锁,Owner 变为 null。
  2. 线程在临界区抛出未捕获异常,JVM 自动释放锁。
  3. 线程调用 wait() 方法,主动释放锁,进入 Wait Set。

三、Java 对象头与 Monitor 关联

Java 中 Monitor 与对象紧密绑定,核心是通过对象头存储 Monitor 相关信息,接下来分析对象头结构与 Monitor 的关联逻辑。

1. 对象头的基本结构

Java 对象头由两部分组成(数组对象多一个 array length 字段):

组成部分 作用
Mark Word 存储对象的锁状态、Monitor 指针、HashCode、GC 年龄等核心信息(重点)。
Klass Word 指向对象的类元数据(如 Object.class),确认对象的类型。

2. Mark Word 与 Monitor 的绑定

Mark Word 是对象头的核心,其结构会随锁状态变化,其中 “重量级锁” 状态下会存储 Monitor 的指针(ptr_to_heavyweight_monitor),直接关联对象与 Monitor。

32 位 JVM Mark Word 结构
锁状态 Mark Word 结构(32 位) 核心存储内容
无锁(Normal) hashcode(25) + age(4) + biased_lock(0) + 01 对象哈希码、GC 年龄
偏向锁(Biased) thread(23) + epoch(2) + age(4) + biased_lock(1) + 01 持有偏向锁的线程 ID
轻量级锁(Lightweight) ptr_to_lock_record(30) + 00 线程栈中锁记录的指针
重量级锁(Heavyweight) ptr_to_heavyweight_monitor(30) + 10 Monitor 对象的指针(核心关联)
GC 标记(Marked) 空 + 11 垃圾回收标记

64 位 JVM Mark Word 结构
锁状态 Mark Word 结构(64 位)
无锁(Normal) unused(25) + hashcode(31) + unused(1) + age(4) + biased_lock(0) + 01
重量级锁 ptr_to_heavyweight_monitor(62) + 10

3. 核心结论

  • 当对象被 synchronized 加锁且升级为重量级锁时,Mark Word 中存储 Monitor 的指针,实现对象与 Monitor 的一一对应。
  • 锁升级的本质:Mark Word 结构的变化,从无锁→偏向锁→轻量级锁→重量级锁,Monitor 仅在重量级锁时被完全激活。

四、Monitor 与 synchronized 的深度绑定

4.1 synchronized 依赖 Monitor 实现同步

  • synchronized 的锁本质:synchronized 是 Monitor 的 “语法糖”,其底层完全依赖 Monitor 机制实现:
    • 同步代码块:通过 monitorenter 指令获取 Monitor 锁,monitorexit 指令释放锁(异常时 JVM 自动插入 monitorexit)。
    • 同步方法:通过方法的 ACC_SYNCHRONIZED 标志位,调用方法时自动获取 / 释放 Monitor 锁(锁对象为 this 或类对象)。
  • 代码层面验证
    // 同步代码块(锁对象为 lock)
    static final Object lock = new Object();
    synchronized (lock) {
        // 临界区:依赖 Monitor 保证互斥
        counter++;
    }
    
    // 同步方法(锁对象为 this)
    public synchronized void increment() {
        // 临界区:底层是 Monitor 锁
        counter++;
    }
    

4.2 Monitor 锁的状态升级(JVM 优化)

Java 为提升 Monitor 性能,引入了锁状态升级机制(无锁 → 偏向锁 → 轻量级锁 → 重量级锁),不同状态对应 Monitor 的不同实现方式:

锁状态 适用场景 Monitor 实现方式 性能特点
无锁 无线程竞争 未初始化 Monitor 无需锁开销
偏向锁 单线程重复获取锁 Monitor 记录偏向线程 ID,无需竞争 几乎无锁开销(仅 CAS 操作)
轻量级锁 少量线程交替竞争 线程通过 CAS 操作尝试获取锁,自旋等待 无内核态阻塞开销,适合短临界区
重量级锁 多线程同时竞争 依赖操作系统互斥量(Mutex)实现 Monitor 阻塞 / 唤醒开销大,适合长临界区
  • 核心逻辑:锁状态只能升级不能降级,通过自适应策略(如自旋次数、竞争强度)动态切换,平衡性能与并发安全性。

4.3 线程八锁与 Monitor 锁对象判定(面试重点)

Monitor 的核心是 “锁对象唯一”,线程八锁本质是判定 synchronized 对应的 Monitor 锁对象,以下是高频场景:

场景 代码示例 锁对象(Monitor) 执行结果 核心结论
1 同一对象调用两个同步成员方法 this(当前对象的 Monitor) 串行执行(12 或 21) 同一 Monitor 互斥
2 同一对象调用同步方法 + 非同步方法 同步方法锁 this,非同步方法无锁 非同步方法先执行 非同步方法不参与 Monitor 竞争
3 不同对象调用同步静态方法 类对象(Class 的 Monitor) 串行执行 类对象的 Monitor 全局唯一
4 同一对象调用同步成员方法 + 同步静态方法 成员方法锁 this,静态方法锁 Class 并行执行 两个不同的 Monitor,不互斥

五、Monitor 实战案例(基于等待 / 通知机制)

5.1 案例 1:线程安全计数器(Monitor 互斥性)

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MonitorCounter {
    // 共享资源
    private int counter = 0;
    // 锁对象(关联 Monitor)
    private final Object lock = new Object();

    // 自增方法(依赖 Monitor 保证原子性)
    public void increment() {
        synchronized (lock) { // monitorenter:获取 Monitor 锁
            counter++;
            log.debug("线程 {} 执行自增,counter = {}", Thread.currentThread().getName(), counter);
        } // monitorexit:释放 Monitor 锁
    }

    public int getCounter() {
        synchronized (lock) {
            return counter;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MonitorCounter counter = new MonitorCounter();
        // 3 个线程并发自增 1000 次
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.increment();
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.increment();
        }, "t2");
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.increment();
        }, "t3");

        t1.start();
        t2.start();
        t3.start();
        t1.join();
        t2.join();
        t3.join();

        log.debug("最终 counter = {}", counter.getCounter()); // 稳定输出 3000
    }
}
  • 核心原理:通过 synchronized 绑定 Monitor,保证 counter++ 临界区同一时间仅一个线程执行,避免指令交错。

5.2 案例 2:生产者 - 消费者模型(Monitor 协作性)

import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.Queue;

@Slf4j
public class MonitorProducerConsumer {
    // 任务队列(共享资源)
    private final Queue<String> taskQueue = new LinkedList<>();
    // 队列容量
    private static final int CAPACITY = 3;
    // 锁对象(关联 Monitor)
    private final Object lock = new Object();

    // 生产者:添加任务
    public void produce(String task) throws InterruptedException {
        synchronized (lock) {
            // 队列满时,等待消费者消费(进入 Wait Set)
            while (taskQueue.size() >= CAPACITY) {
                log.debug("队列满,生产者 {} 等待", Thread.currentThread().getName());
                lock.wait(); // 释放 Monitor 锁,进入 Wait Set
            }
            // 生产任务
            taskQueue.offer(task);
            log.debug("生产者 {} 生产任务:{},队列大小:{}", Thread.currentThread().getName(), task, taskQueue.size());
            lock.notifyAll(); // 唤醒消费者(从 Wait Set 移至 Entry Set)
        }
    }

    // 消费者:获取任务
    public String consume() throws InterruptedException {
        synchronized (lock) {
            // 队列空时,等待生产者生产(进入 Wait Set)
            while (taskQueue.isEmpty()) {
                log.debug("队列空,消费者 {} 等待", Thread.currentThread().getName());
                lock.wait(); // 释放 Monitor 锁,进入 Wait Set
            }
            // 消费任务
            String task = taskQueue.poll();
            log.debug("消费者 {} 消费任务:{},队列大小:{}", Thread.currentThread().getName(), task, taskQueue.size());
            lock.notifyAll(); // 唤醒生产者(从 Wait Set 移至 Entry Set)
            return task;
        }
    }

    public static void main(String[] args) {
        MonitorProducerConsumer pc = new MonitorProducerConsumer();

        // 2 个生产者线程
        for (int i = 0; i < 2; i++) {
            int producerId = i;
            new Thread(() -> {
                try {
                    for (int j = 0; j < 5; j++) {
                        pc.produce("任务-" + producerId + "-" + j);
                        Thread.sleep(500); // 模拟生产耗时
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "生产者-" + producerId).start();
        }

        // 2 个消费者线程
        for (int i = 0; i < 2; i++) {
            int consumerId = i;
            new Thread(() -> {
                try {
                    for (int j = 0; j < 5; j++) {
                        pc.consume();
                        Thread.sleep(1000); // 模拟消费耗时
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "消费者-" + consumerId).start();
        }
    }
}
  • 核心原理:利用 Monitor 的 Wait Set 和条件变量,实现生产者与消费者的协作:
    • 队列满时,生产者调用 wait() 释放锁,进入等待;
    • 队列空时,消费者调用 wait() 释放锁,进入等待;
    • 一方完成操作后,调用 notifyAll() 唤醒另一方,避免死等。

六、相关知识点

6.1 Monitor 与 Lock(ReentrantLock)的对比

特性 Monitor(synchronized 底层) Lock(ReentrantLock)
锁获取方式 隐式获取(monitorenter 显式获取(lock()
锁释放方式 隐式释放(monitorexit/ 异常自动释放) 显式释放(unlock(),必须在 finally 中)
可中断性 否(BLOCKED 状态不可中断) 是(lockInterruptibly()
超时获取 是(tryLock(long)
公平锁支持 否(默认非公平,JDK 1.6+ 有自适应公平策略) 是(构造函数指定 true
多条件变量 否(仅一个 Wait Set) 是(newCondition(),多个 Condition 对应多个等待队列)
锁状态查询 无直接 API(需通过线程状态判断) 有(isLocked()/getHoldCount()
适用场景 简单同步场景(代码简洁) 复杂同步场景(如超时、中断、多条件)

6.2 Monitor 常见问题与避坑指南

  1. 虚假唤醒
    • 问题:wait() 可能在未被 notify()/notifyAll() 唤醒的情况下返回(JVM 底层实现导致)。
    • 解决:必须用 while 循环判断条件,而非 if(如案例 2 中 while (taskQueue.isEmpty())),唤醒后重新校验条件。
  2. wait()/notify() 必须在 synchronized 块中
    • 原因:调用这两个方法前,线程必须持有 Monitor 锁(否则抛 IllegalMonitorStateException)。
    • 逻辑:wait() 需要先释放锁,notify() 需要唤醒等待线程竞争锁,均依赖 Monitor 上下文。
  3. notify() 与 notifyAll() 的选择
    • notify():随机唤醒一个线程,可能导致其他线程长期等待(饥饿)。
    • notifyAll():唤醒所有等待线程,避免饥饿,但可能增加锁竞争开销。
    • 建议:多线程协作时优先用 notifyAll(),除非能明确保证唤醒单个线程即可满足条件。
  4. 锁膨胀与性能影响
    • 问题:Monitor 从偏向锁升级为重量级锁后,性能开销大幅增加(内核态阻塞 / 唤醒)。
    • 优化:尽量减小临界区代码长度,避免长时间持有锁;避免多线程同时竞争同一把锁。

6.3 Monitor 的核心特性总结

  1. 互斥性:通过 Owner 机制保证临界区独占访问,解决竞态条件。
  2. 可重入性:同一线程可多次获取同一 Monitor 锁(Mark Word 记录锁重入次数),避免死锁。
  3. 协作性:通过 Wait Set 和条件变量支持线程间高效通信,无需轮询(降低 CPU 开销)。
  4. 原子性保障:临界区代码在 Monitor 保护下,视为原子操作(避免指令交错)。

6.4 底层字节码分析(Monitor 指令级实现)

以同步代码块为例,查看 monitorenter 和 monitorexit 指令:

// 原代码
synchronized (lock) {
    counter++;
}

// 编译后的字节码(关键部分)
0: aload_1         // 加载锁对象 lock 到操作数栈
1: monitorenter    // 获取 Monitor 锁(进入临界区)
2: aload_0         // 加载当前对象
3: dup             // 复制对象引用
4: getfield        // 获取 counter 字段
7: iconst_1        // 压入常量 1
8: iadd            // counter + 1
9: putfield        // 赋值给 counter
12: aload_1        // 加载锁对象
13: monitorexit    // 释放 Monitor 锁(正常退出)
14: goto 22        // 跳至方法结束
17: astore_2       // 捕获异常
18: aload_1        // 加载锁对象
19: monitorexit    // 释放 Monitor 锁(异常退出)
20: aload_2        // 抛出异常
21: athrow
22: return
  • 关键:monitorenter 对应锁获取,monitorexit 对应锁释放,异常时 JVM 自动插入 monitorexit,保证锁一定释放。

七、核心 API 详解(Monitor 相关)

API 方法 作用 关键注意点
void wait() 释放 Monitor 锁,进入 Wait Set(无超时等待) 必须在 synchronized 块中;可能虚假唤醒,需配合 while 循环;唤醒后重新竞争锁
void wait(long timeout) 释放 Monitor 锁,进入 TIMED_WAITING 状态(超时自动唤醒) 超时时间单位为毫秒;其他注意点同 wait()
void notify() 从 Wait Set 中随机唤醒一个线程,移至 Entry Set 竞争锁 必须在 synchronized 块中;仅唤醒一个线程,可能导致饥饿
void notifyAll() 唤醒 Wait Set 中所有线程,均移至 Entry Set 竞争锁 必须在 synchronized 块中;避免饥饿,多线程协作优先使用
synchronized 代码块 / 方法 隐式获取 / 释放 Monitor 锁 锁对象为指定对象 /this/ 类对象;自动重入;异常时自动释放锁

八、总结

Monitor 是 Java 并发编程的核心同步机制,是 synchronized 的底层实现基础,核心价值在于通过互斥性保证临界区安全,通过协作性支持线程间高效通信

  • 核心组成:Entry Set(锁竞争队列)、Owner(锁持有者)、Wait Set(等待队列);
  • 核心流程:线程竞争锁(Entry Set → Owner)、线程协作(Owner → Wait Set → Entry Set);
  • 关键拓展:锁状态升级(优化性能)、与 Lock 的对比、虚假唤醒避坑;
  • 适用场景:简单同步用 synchronized(依赖 Monitor),复杂同步用 Lock(灵活拓展)。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐