没懂 synchronized 底层如何实现和锁的升降级,被面试官暴打一顿
引言:在 Java 多线程编程中,深入理解 synchronized 关键字的内部工作原理,以及掌握锁的升级和降级机制,对于每个 Java 开发者而言都是一个不小的挑战。如果你还对 synchronized 的底层实现方式感到困惑,或者不清楚什么是锁的升级与降级,那么在面试或实际开发中遇到这类问题时,可能会感到无从下手。但别担心,本文的目的就是要带你深入了解 synchronized。我们将探讨 synchronized 如何在 JVM 层面上实现同步,以及锁在不同竞争情况下如何转换其状态,从而帮助你在讨论多线程同步控制的时候,能够自信且清晰地阐述它的工作原理和应用场景。
题目
没懂 synchronized 底层如何实现和锁的升降级,被面试官暴打一顿
推荐解析
深入理解 synchronized
在 Java 中,synchronized
是实现同步的基本方法之一,既可以修饰方法也可以修饰代码块。其内部实现依赖于 JVM 层面的监视器锁(Monitor Lock),但它的工作原理和锁的状态变化较为复杂,涉及锁的升级与降级。
synchronized 的底层实现
监视器锁(Monitor)
synchronized
关键字的实现依赖于 JVM 内部的一个概念叫做监视器锁(Monitor)。每个对象都有一个监视器锁,当这个对象被用作同步时,执行同步代码的线程必须首先获得这个对象的监视器锁。
对象头(Object Header)
在 JVM 中,对象在内存中的布局包括对象头、实例数据和对齐填充。对象头包含了对象的运行时数据,如哈希码、GC 分代年龄、锁状态标志等。对于锁的实现,特别是锁的状态,是通过对象头中的一部分来表示的。
锁的状态
1)轻量级锁:当一个线程进入同步块时,如果目标对象没有被锁定,JVM 会在当前线程的栈帧中创建用于锁记录的空间,尝试使用 CAS 操作将对象头的 Mark Word 更新为指向锁记录的指针。如果成功,当前线程获得锁,进入轻量级锁状态。
2)重量级锁:如果轻量级锁的竞争失败(例如,另一个线程已持有该锁),锁会膨胀为重量级锁。此时,JVM 会在对象头上设置指向一个监视器(Monitor)的指针,线程将会进入阻塞状态,直到锁可用。
3)偏向锁:JVM 也采用了偏向锁来优化同步性能。当锁被第一次获取时,对象头会被标记为偏向模式,并记录获取它的线程 ID。之后,该线程进入同步块时不需要进行任何同步操作。如果在偏向模式下,另一个线程尝试获取锁,偏向模式会被撤销,锁会升级为轻量级锁或重量级锁。
锁的升级与降级
锁升级
1)从无锁到偏向锁:当一个线程首次访问同步块时,会将对象头设置为偏向锁状态,标记为该线程所有。
2)从偏向锁到轻量级锁:当有另一个线程尝试获取已被偏向的锁时,如果持有偏向锁的线程不活跃,JVM 将取消偏向模式,升级锁为轻量级锁。
3)从轻量级锁到重量级锁:当轻量级锁的竞争发生时,即 CAS 失败,锁会升级为重量级锁,线程进入阻塞状态,等待锁释放。
锁降级
锁降级是指锁状态从重量级锁转变为轻量级锁或偏向锁的过程。在 Java 的 synchronized
实现中,锁降级的概念并不显著,主要是因为一旦锁升级为重量级锁,就很少会降级。锁的降级主要出现在 JVM 对锁状态的优化过程中,例如在锁竞争减少时,JVM 可能会在未来的某个时间点自动进行优化,但这不是显式控制的过程。
加深记忆的例子
想象一下,你住在一个有多间房间的大房子里,每个房间都有一扇门,这些门代表不同的锁状态。
1)无锁到偏向锁:这就像家里的房间通常是开着的,但当你晚上回家并进入你的房间时,你会把门轻轻地关上并转上一圈小锁。这个小锁就是偏向锁,它表示这个房间现在偏向于你,只要你在里面,别人就假设不能进入。但这并不阻止家里的其他人敲门。
2)偏向锁到轻量级锁:如果你的房间门(偏向锁)关着,你的室友想进入时,他们会敲门。这时,你意识到需要更多的隐私,于是你会起来,从里面用钥匙把门锁上,然后再打开让室友进来。这个上锁的动作就像是轻量级锁的获取,需要更明确的操作(比如 CAS 操作)来确保门的状态。
3)轻量级锁到重量级锁:如果你的房间里正在举行派对,很多人想进来,但门只能让一个人一次进入。于是你决定使用门卫(监视器锁),让他决定谁能进来,谁需要等待。这时,门的锁就升级成了重量级锁,确保了同一时间只有一个人可以进入房间,其他人需要在外面等候。
锁的升级与降级:
1)锁升级:这个过程就像是门的锁从一个简单的转把锁(偏向锁),到需要钥匙才能打开的锁(轻量级锁),再到需要门卫控制的高级锁系统(重量级锁)。随着需要控制的人数增多,门的锁系统也变得更加复杂和安全。
2)锁降级:在 Java 中,锁的降级不常见,因为一旦使用了重量级锁,系统就倾向于保持这种状态。但如果将其比喻到现实生活中,可以想象在一天结束时,派对结束了,你告诉门卫他可以回家了,然后你自己使用钥匙锁上门,最终可能只是简单地关上门而不上锁,这就相当于锁的状态从重量级锁“降级”回到了更轻量的状态。
其他补充
鱼聪明 AI 的回答:
鱼聪明 AI 地址:https://www.yucongming.com/
JVM中的锁优化技术
除了已提及的偏向锁、轻量级锁和重量级锁,JVM在运行时还会采用如下几种技术进一步优化锁的性能:
锁粗化(Lock Coarsening):如果 JVM 检测到一系列的连续锁获取和释放操作,且都是对同一个对象,那么 JVM 会尝试合并这些操作,扩大锁的范围,从而减少锁获取和释放的次数。这种优化通常发生在循环中对同一个对象进行加锁的场景。 锁消除(Lock Elimination):JVM 在 JIT 编译阶段通过逃逸分析来判断同步代码块是否可能被多线程访问。如果一个对象不会逃逸出当前线程,那么对这个对象的同步操作实际上是不必要的,因此 JVM 可以安全地消除这部分代码的锁操作。
优化策略
JVM 采用了多种优化策略来减少锁操作的开销,例如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)和自旋锁等。这些优化旨在减少不必要的锁竞争开销,提高多线程程序的执行效率。
推荐文章和书籍
文章:https://zhuanlan.zhihu.com/p/86293659
书籍:《 Java 核心技术卷 I 》
欢迎交流
在深入探索 Java 多线程编程的精髓时,理解 synchronized 关键字的底层实现,以及锁的升级与降级机制,无疑是每个 Java 开发者成长过程中的一项重要挑战。通过本文,我们旨在揭开 synchronized 在 JVM 中工作原理的神秘面纱,详细探讨其如何管理锁的状态转换,从而在面对高并发和多线程编程时,你能够更加自信和高效地设计同步控制策略、提升应用性能,并优化资源管理。为了让你对 synchronized 的底层机制有更深刻的理解,并鼓励在技术社区中的知识分享和讨论,我们提出以下三个问题,期待你的深入思考和积极参与:
1)锁的状态转换探讨:在 synchronized 使用过程中,锁有哪些状态?请描述这些状态(无锁、偏向锁、轻量级锁、重量级锁)之间的转换过程,并解释在何种场景下会发生这些转换。这些转换的发生对系统性能有何影响?
2)锁的升级与降级机制:详细解释 synchronized 锁的升级与降级机制。在 Java 多线程环境中,为什么需要锁的升级和降级?请给出实际的编程场景,说明在这些场景下,锁的升级或降级如何帮助优化性能和资源利用。
3)synchronized 的底层实现:探讨 synchronized 关键字在 JVM 层面是如何实现的。请详细描述 synchronized 的操作是如何通过对象监视器(Monitor)来控制方法或代码块的同步访问的。此外,JVM 为了优化 synchronized 的性能,采取了哪些内部优化措施(例如锁粗化、锁消除等)?
通过对上述问题的深入探讨,我们不仅能加强对 synchronized 关键字底层实现和锁机制的理解,还能学习到如何根据实际的并发需求,选择和设计出最合适的同步策略。这不仅是对个人技术深度的提升,也是对整个 Java 社区知识共享与进步的贡献。我们鼓励每位读者在评论区留下自己的见解和经验,共同促进 Java 并发编程领域的发展。期待你的参与和贡献,让我们携手在 Java 并发编程的道路上不断前行。
点燃求职热情!每周持续更新,海量面试题等你挑战!赶紧关注面试鸭公众号,轻松备战春招和暑期实习!