讲师中心 微信公众号
AI工具推荐 视频效率加速

详解synchronized的MonitorEnter与MonitorExit字节码指令

星杰姑娘_1559

星杰姑娘_1559

发布时间:2026-02-22 15:13:03

|

838人浏览过

|

来源于php中文网

原创

monitorenter和monitorexit是JVM底层字节码指令,分别负责获取和释放对象锁,通过对象头mark word与ObjectMonitor协作实现可重入、异常安全的同步机制。

详解synchronized的monitorenter与monitorexit字节码指令

monitorenter 和 monitorexit 到底在干啥

它们不是 Java 代码里的函数,而是 JVM 执行同步块时插入的底层字节码指令——monitorenter 负责“抢锁”,monitorexit 负责“放锁”。你写 synchronized(obj) { ... },编译器就自动给你包上这一对指令;它不关心 obj 是不是 null、是不是 final,只认这个引用值。

关键点在于:锁的归属和计数全靠对象头里的 mark word 和关联的 ObjectMonitor 结构维护,而这两条指令就是触发这套机制的开关。

  • monitorenter 执行时,JVM 检查目标对象是否已被当前线程持有;如果是,计数器 +1(可重入);如果不是,尝试获取锁,失败则进 EntryList 阻塞
  • monitorexit 每执行一次,计数器 -1;只有计数器归零时,锁才真正释放,其他线程才可能被唤醒
  • 哪怕同步块里抛了未捕获异常,JVM 也保证至少有一个 monitorexit 执行——这是通过字节码里的 Exception table 实现的,不是 try-finally 编译出来的

为什么 javap 看到两个 monitorexit?

因为安全。一个给正常流程用,一个专为异常兜底——你看 javap -v 输出,总能看到类似这样的结构:

  0: aload_0
  1: dup
  2: astore_1
  3: monitorenter
  4: getstatic     #2
  7: ldc           #3
  9: invokevirtual #4
 12: aload_1
 13: monitorexit   <-- 正常出口
 14: goto          22
 17: astore_2
 18: aload_1
 19: monitorexit   <-- 异常出口
 20: aload_2
 21: athrow
 22: return
Exception table:
   from    to  target type
       4    14    17   any
      17    20    17   any

没这个双保险,一旦同步块里 NPE 或 OOM,锁就永远卡住——这比性能问题更致命。

  • 别自己手写 monitorenter/monitorexit:它们是 JVM 内部指令,不能直接调用,也不在 Java API 里
  • 不要以为加了 try-catch 就能替代这个机制:JVM 的异常表处理是字节码层的,比 Java 层的异常处理更早、更底层
  • 如果反编译发现只有一个 monitorexit,那基本说明代码路径极简(比如空块),或编译器做了激进优化(少见,JDK 8+ 后基本都保留双出口)

ACC_SYNCHRONIZED 和 monitorenter 的区别在哪

根本不是“两种实现”,而是同一套机制的两种字节码表达形式:ACC_SYNCHRONIZED 是方法级的“快捷方式”,monitorenter/monitorexit 是代码块级的“手动档”。

比如 public synchronized void m() { } 编译后不会出现那两条指令,而是在方法属性里打上 ACC_SYNCHRONIZED 标志;JVM 运行时看到这个标志,会自动在方法入口插 monitorenter,出口插 monitorexit(同样带异常兜底)。

  • 静态同步方法锁的是 Class 对象,实例方法锁的是 this,但底层都是对某个对象执行 monitorenter
  • ACC_SYNCHRONIZED 方法无法指定锁对象,灵活性不如代码块;但少了显式对象引用,字节码更紧凑
  • 二者锁升级路径完全一致(偏向锁 → 轻量级锁 → 重量级锁),因为最终都落到同一个 ObjectMonitor 实例上

容易被忽略的坑:null、锁对象生命周期、锁粒度

monitorenter 遇到 null 会直接抛 NullPointerException,而且是在字节码执行阶段——不是进入方法后才检查。这意味着,如果你传了个可能为 null 的锁对象,NPE 会发生在同步块第一行之前,且无法被该块内的 catch 捕获。

  • 永远不要用可能为 null 的变量做锁对象,比如 synchronized(map.get("key")) —— 万一 key 不存在,map.get 返回 null,直接炸
  • 锁对象要是长期稳定存在的;比如用局部 new 出来的 new Object() 当锁,每次调用都新建,等于没锁
  • 锁粒度要匹配场景:锁 this 可能导致整个对象被串行化;锁内部私有 final 字段(如 private final Object lock = new Object())才是常见安全做法
  • monitor 的重量级状态(即真正调用 OS mutex)一旦触发,线程挂起/唤醒开销极大;高频短临界区反而比无锁原子操作慢得多

Monitor 不是黑盒,它是 C++ 实现的、和操作系统线程强绑定的结构;看懂 monitorenter 和 monitorexit 的行为,等于摸到了 synchronized 的脉门——其它所有优化(锁消除、锁粗化、自旋)都是在这个基础上叠的。

热门AI工具

更多
咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

549

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1698

2024.03.01

javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

636

2023.11.23

java中void的含义
java中void的含义

本专题整合了Java中void的相关内容,阅读专题下面的文章了解更多详细内容。

371

2025.11.27

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

4116

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

306

2025.12.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4398

2023.08.10

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

630

2025.09.05

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

80

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn