深入拆解 Java 虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
87446 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
模块四:黑科技 (3讲)
深入拆解 Java 虚拟机
15
15
1.0x
00:00/00:00
登录|注册

14 | Java虚拟机是怎么实现synchronized的?

参数-XX:TieredStopAtLevel=1
参数-XX:+PrintBiasedLockingStatistics
验证偏向锁关闭传闻
epoch值
撤销偏向锁
CAS操作记录当前线程地址
锁对象的标记字段
CAS操作
自适应自旋
自旋状态
阻塞加锁失败的线程
总结与实践
偏向锁
轻量级锁
重量级锁
Java虚拟机中synchronized关键字的实现

该思维导图由 AI 生成,仅供参考

在 Java 程序中,我们可以利用 synchronized 关键字来对程序进行加锁。它既可以用来声明一个 synchronized 代码块,也可以直接标记静态方法或者实例方法。
当声明 synchronized 代码块时,编译而成的字节码将包含 monitorenter 和 monitorexit 指令。这两种指令均会消耗操作数栈上的一个引用类型的元素(也就是 synchronized 关键字括号里的引用),作为所要加锁解锁的锁对象。
public void foo(Object lock) {
synchronized (lock) {
lock.hashCode();
}
}
// 上面的Java代码将编译为下面的字节码
public void foo(java.lang.Object);
Code:
0: aload_1
1: dup
2: astore_2
3: monitorenter
4: aload_1
5: invokevirtual java/lang/Object.hashCode:()I
8: pop
9: aload_2
10: monitorexit
11: goto 19
14: astore_3
15: aload_2
16: monitorexit
17: aload_3
18: athrow
19: return
Exception table:
from to target type
4 11 14 any
14 17 14 any
我在文稿中贴了一段包含 synchronized 代码块的 Java 代码,以及它所编译而成的字节码。你可能会留意到,上面的字节码中包含一个 monitorenter 指令以及多个 monitorexit 指令。这是因为 Java 虚拟机需要确保所获得的锁在正常执行路径,以及异常执行路径上都能够被解锁。
你可以根据我在介绍异常处理时介绍过的知识,对照字节码和异常处理表来构造所有可能的执行路径,看看在执行了 monitorenter 指令之后,是否都有执行 monitorexit 指令。
当用 synchronized 标记方法时,你会看到字节码中方法的访问标记包括 ACC_SYNCHRONIZED。该标记表示在进入该方法时,Java 虚拟机需要进行 monitorenter 操作。而在退出该方法时,不管是正常返回,还是向调用者抛异常,Java 虚拟机均需要进行 monitorexit 操作。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Java虚拟机中的锁机制是通过synchronized关键字实现的,包括重量级锁、轻量级锁和偏向锁。重量级锁会阻塞、唤醒请求加锁的线程,适用于多个线程竞争同一把锁的情况。轻量级锁采用CAS操作,针对多个线程在不同时间段申请同一把锁的情况。而偏向锁则适用于锁仅会被同一线程持有的情况。文章介绍了这些锁的实现原理和特点,并提供了实践环节来验证偏向锁的一些传闻。读者可以通过参数来观察各类锁的个数,以及验证调用Object.hashCode()对偏向锁的影响。这篇文章深入浅出地介绍了Java虚拟机中synchronized关键字的实现方式,适合想要深入了解Java虚拟机锁机制的读者阅读。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Java 虚拟机》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(79)

  • 最新
  • 精选
  • 恩,今天才补上小结,因为听不明白了,后来反复听以及补上锁的相关知识才有点明白。 我认为雨迪确实应该补上点图,这样才更容易理解,否则确实抽象,另外,我觉得讲解的次序有点小问题。 如果这样讲就更容易理解了(个人见解) 1:讲解一下锁的本质,锁到底是个什么东西?锁的特点容易理解,毕竟都见过摸过用过 2:讲解一下锁的分类和特点,什么表锁、行锁、自旋锁、可重用锁、轻量锁、重量锁、阻塞锁、线程锁、进程锁、分布式锁、偏向锁等等吧!都是站在不同的角度或层级根据锁的特点,为了好区分给锁起的名字 3:讲解一下JVM中的各种锁,讲解一下他们的特点和实现,然后讲解一下咱们本节的主角是属于哪一种或哪几种锁 4:我的理解,锁的本质-在程序世界里是一种保证资源正确竞争的机制,如果没有对同一资源竞争也就没有了锁存在的意义,在计算世界中资源引起竞争的核心基本是空间,有其是计算机的内存空间,当然数据肯定也是一种引起激烈竞争的资源,不过往往会体现到空间上去,因为计算机中的数据必定存于某空间地址之中的 5:感觉明白可重用锁的实现原理了,这个也是雨迪讲的最细致的一种实现方式,恩,非常感谢🙏

    作者回复: 多谢建议! 本文的讲解流程是从通用锁算法到针对特殊情况的锁算法来的。一开始monitorenter是用重型锁的,然后为了针对没有竞争锁的情况有了轻型锁,再然后为了针对只有一个线程持有某个锁的情况有了偏向锁。

    2018-09-01
    4
    29
  • 爪哇夜未眠
    太抽象了,老师能画点儿图吗……

    作者回复: 不好意思哈,因为网上有很多图,忘了放个链接了。 你可以参考wiki.openjdk.java.net/display/HotSpot/Synchronization中的图。

    2018-08-22
    24
  • Shine
    “当进行解锁操作时,如果当前锁记录(你可以将一个线程的所有锁记录想象成一个栈结构,每次加锁压入一条锁记录,解锁弹出一条锁记录,当前锁记录指的便是栈顶的锁记录)的值为 0,则代表重复进入同一把锁,直接返回即可。” 这种情况也需要弹出当前锁记录的吧? 不然锁记录一直是0不变了。 如果是我这样理解的话,重复获取同一把锁的话,不是简单地清零,而应该是把0作为一条新的锁记录压入栈顶。 不知道我这样理解对不?请老师指点

    作者回复: 对的!赞

    2018-08-26
    2
    18
  • NEO🍋
    老师关于偏向锁有个疑问 “它针对的是锁仅会被同一线程持有的情况。” 如果只有一个线程持有锁 还有必要加锁吗?

    作者回复: 哈,这个属于应用程序的问题,JVM只是观察到这种情况,并尝试做出优化。 有一种可能,就是很长一段时间内,只有一个线程频繁加锁,后面换成另外的线程,这样前面那段时间可以用偏向锁。

    2018-08-22
    13
  • Geek_987169
    老师请教个问题: 1:锁从偏向一直到重量级的过程是"单向不可逆"的,这个"单向不可逆"是限制在对象的整个生命周期,还是在对象到达了某个状态后再次有线程使用其作为锁对象还会继续重复这个过程?从每撤销一次对象的epoch值就会+1,而这个+1代表的就是偏向锁升级为轻量级锁,而每个对象又维护了一个epoch值代表对象撤销次数(偏向锁->轻量级锁次数),是不是就代表这个锁升级的过程会在不同的时间段重复发生n词? 2:为什么要设置一个最大的撤销次数(epoch值),意义在哪里?

    作者回复: 1. 单向不可逆 针对一个对象的整个生命周期。 epoch+1发生在多次同一类型的实例的偏向锁撤销之后,存放在类型(Class)那里的。 2. 当频繁检测到某个类的实例出现撤销偏向锁的,就代表这个类不适合用来搞偏向锁。

    2018-10-18
    8
  • 唯一
    老师,问一下加锁实际上都是加在当前线程吗

    作者回复: 这个说法有点歧义。 按我的理解,你应该在问是否为当前线程获得这把锁?那么答案是对的,一直是当前线程获得这把锁。 另外,锁是加在目标锁对象上的。

    2018-08-22
    6
  • 何yuan
    一直认为synchronized是重量锁,是否也不一定?jvm处理的时候是先将当偏向锁处理,然后慢慢膨胀为重量级锁的是吗?

    作者回复: 默认情况下是的。以前有个延缓毫秒数-XX:BiasedLockingStartupDelay,一开始用轻量级锁,在启动四秒之后才开始用偏向锁。我记得Java 9还是10默认值改为0了。

    2018-08-22
    2
    6
  • 木心
    很多文章说 自旋 是在轻量级锁中发生的 《Java并发编程的艺术》 但是在这里 自旋 是在重量级锁中 这个怎么解释呢? https://www.aimoon.site/blog/2018/05/21/biased-locking/

    作者回复: 自旋本质是空转cpu等待,只有在别人拿着锁,自己请求锁的情况下发生。偏向锁无需此步骤,栈锁别人没有持有锁,也不需要自旋

    2019-11-28
    6
    5
  • Leon Wong
    老师你好,本课程在介绍轻量级锁的时候,没提及轻量级锁在其他线程占用改锁的的时候,是否会进入自旋状态,我先前的理解是,轻量级锁在被其他线程占用的时候,会进入短暂的自旋状态,当自旋达到一定的阈值后,膨胀为重量级锁,阻塞当前线程,不知道我这么理解是否正确?

    作者回复: 我印象中不会自旋,直接膨胀。 轻量级锁的假设是,不同线程拿同一把锁的时间没有overlap。一旦有了overlap,即需要竞争锁的情况,那么假设失效,需要膨胀为重量锁。 如果乐观点的话,猜测只有这一次假设失效,那也可以自旋一会再膨胀。不过我记得没有这么乐观。 你可以自己读hotspot的源代码,share/runtime/synchronizer.cpp ObjectSynchronizer:fast_enter

    2018-09-17
    3
    4
  • 贾智文
    假设当前锁对象的标记字段为 X…XYZ,Java 虚拟机会比较该字段是否为 X…X01。 老师请问这个x……x01是什么,根据什么来的呢?

    作者回复: 这里可能没写清楚。我指的是标记字段的bits是否为X..X01,其中X..X是从原本的标记字段拷过来的。

    2018-08-22
    3
    4
收起评论
显示
设置
留言
79
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部