🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论
🔥🔥🔥(源码获取 + 调试运行 + 问题答疑)🔥🔥🔥 有兴趣可以联系我
🔥🔥🔥 文末有往期免费源码,直接领取获取(无删减,无套路)
我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。
《深入剖析synchronized:从对象头到重量级锁的完整升级路径》
《Java对象锁的进化史:偏向锁→轻量级锁→重量级锁的智慧》
《synchronized底层揭秘:为何你的锁会从用户态走向内核态?》
正文
一、对象锁:Java并发的基石
在Java并发编程的世界里,synchronized关键字作为最古老、最基础的同步机制,经历了二十多年的演进与优化。从最初的性能瓶颈到如今的高度优化,对象锁的实现原理体现了JVM开发者在性能与功能之间的精妙平衡。
每个Java对象都与一个内置锁(monitor)相关联,这个锁的实现并非一成不变,而是根据竞争情况动态调整。理解这个动态过程,对于编写高性能的并发程序至关重要。
二、对象头:锁信息的存储中心
要理解synchronized的工作原理,首先需要了解Java对象的内存布局。每个对象在堆中都包含三个部分:对象头、实例数据和对齐填充。其中,对象头是锁机制的核心载体。
对象头的组成:
-
Mark Word(标记字):存储对象的哈希码、GC分代年龄、锁状态等信息
-
Klass Pointer(类型指针):指向对象所属的类元数据
-
数组长度(仅数组对象):记录数组的长度
Mark Word在不同锁状态下会存储不同的内容,这种复用设计体现了极致的空间优化思想。
三、Mark Word的结构演变
Mark Word在32位和64位JVM中的结构有所不同,但其设计理念一致:用最小的空间存储最多的信息。
32位JVM的Mark Word布局:
| 锁状态 | 25位 | 4位 | 1位(偏向锁标志) | 2位(锁标志) |
|---|---|---|---|---|
| 无锁 | 对象的hashCode | 分代年龄 | 0 | 01 |
| 偏向锁 | 线程ID + Epoch | 分代年龄 | 1 | 01 |
| 轻量级锁 | 指向栈中锁记录的指针 | - | - | 00 |
| 重量级锁 | 指向互斥量(monitor)的指针 | - | - | 10 |
| GC标记 | 空 | - | - | 11 |
这种设计使得JVM能够在运行时根据锁状态动态解释Mark Word的含义,实现了极致的空间利用率。
四、锁升级:智能的优化策略
锁升级(锁膨胀)是synchronized性能优化的核心策略。JVM并不是一开始就使用重量级锁,而是根据竞争情况逐步升级,这个过程完全自动且对开发者透明。
4.1 偏向锁(Biased Locking)
适用场景:实际上没有竞争或只有一个线程使用的场景
偏向锁的核心理念是"偏向第一个访问的线程"。当线程第一次获得锁时,JVM会在对象头和栈帧中记录偏向的线程ID。之后该线程再次进入同步块时,不需要进行CAS操作,只需简单检查对象头的线程ID是否匹配。
偏向锁的获取流程:
-
检查Mark Word中的偏向锁标志和线程ID
-
如果可偏向且线程ID匹配,直接进入同步块
-
如果未偏向,通过CAS竞争偏向锁
-
如果已偏向但线程ID不匹配,开始撤销偏向锁
偏向锁的引入基于这样一个观察:在大多数应用中,锁在大部分时间内不会被多个线程竞争。
4.2 轻量级锁(Lightweight Locking)
当偏向锁被撤销后,锁会升级为轻量级锁。轻量级锁的核心思想是:在没有真实竞争的情况下,通过CAS操作避免操作系统的互斥操作。
轻量级锁的加锁过程:
-
在当前线程的栈帧中创建锁记录(Lock Record)
-
将对象头的Mark Word复制到锁记录中(Displaced Mark Word)
-
使用CAS将对象头的Mark Word替换为指向锁记录的指针
-
如果CAS成功,当前线程获得锁
-
如果CAS失败,说明存在竞争,开始自旋或升级
轻量级锁的"轻量"体现在它避免了用户态到内核态的切换,所有操作都在用户空间完成。
4.3 自旋优化
在轻量级锁竞争失败后,线程不会立即挂起,而是进行自旋等待。自旋的本质是让线程执行一个忙循环,期待在短时间内锁能被释放。
自旋策略的演进:
-
早期:固定次数自旋(如10次)
-
现在:自适应自旋(根据上次自旋成功情况动态调整)
自适应自旋是JVM智能化的体现:如果某个锁上次自旋成功获得了锁,那么这次会允许更长的自旋时间;反之,则减少自旋甚至直接升级。
4.4 重量级锁(Heavyweight Locking)
当轻量级锁自旋失败后,锁最终会升级为重量级锁。重量级锁基于操作系统的互斥量(mutex)实现,涉及用户态到内核态的切换。
重量级锁的核心组件:
-
ObjectMonitor:JVM层的monitor对象
-
cxq:竞争队列,存储等待锁的线程
-
EntryList:入口队列
-
WaitSet:等待队列(调用wait()的线程)
重量级锁的加锁过程涉及内核态操作,线程上下文切换开销较大,但能够正确处理复杂的竞争场景。
五、锁升级的触发条件
理解锁升级的触发条件对于性能调优至关重要:
偏向锁→轻量级锁:当有第二个线程尝试获取锁时 轻量级锁→重量级锁:自旋失败或等待线程数超过阈值 直接重量级锁:调用了wait()、notify()等方法
六、重量级锁的底层实现
重量级锁的实现依赖于ObjectMonitor对象,其核心方法包括:
class ObjectMonitor {
void* volatile _owner; // 当前持有锁的线程
ObjectWaiter* volatile _cxq; // 竞争队列
ObjectWaiter* volatile _EntryList; // 入口队列
ObjectWaiter* volatile _WaitSet; // 等待队列
volatile int _recursions; // 重入次数
// ...
};
当线程尝试获取重量级锁时:
-
通过CAS尝试设置_owner字段
-
如果失败,进入cxq队列等待
-
等待期间线程被挂起,进入内核态阻塞
-
锁释放时,从队列中唤醒等待线程
七、锁升级的目的与价值
锁升级策略体现了JVM在性能优化上的深度思考:
1. 性能与功能的平衡
-
无竞争时:偏向锁,几乎零开销
-
轻度竞争:轻量级锁,用户态CAS
-
激烈竞争:重量级锁,保证正确性
2. 适应真实场景 大多数应用中的锁竞争都是短暂的,升级策略适应了这种"局部性"特征。
3. 避免过度优化 重量级锁虽然开销大,但在高竞争下比持续自旋更有效。
八、synchronized vs AQS实现的锁
虽然都是锁实现,但synchronized和AQS在设计和适用场景上存在显著差异:
| 特性 | synchronized | AQS实现的锁 |
|---|---|---|
| 实现方式 | JVM内置,自动优化 | Java代码,手动控制 |
| 锁升级 | 自动完成,对开发者透明 | 无自动升级,策略固定 |
| 灵活性 | 相对固定 | 高度可定制 |
| 功能特性 | 基础同步 | 支持条件队列、公平性等 |
| 性能特点 | 低竞争下性能极佳 | 高竞争下更可控 |
| 使用复杂度 | 简单易用 | 需要深入理解 |
九、实践建议与性能调优
基于对锁原理的理解,我们可以得出以下实践建议:
1. 减少锁竞争
-
缩小同步代码块范围
-
使用读写分离策略
-
避免在锁内进行IO操作
2. 合理选择锁类型
-
低竞争场景:优先synchronized
-
高竞争或需要高级特性:考虑ReentrantLock
3. 监控锁状态 使用JVM参数监控锁升级情况:
-XX:+PrintFlagsFinal | grep BiasedLocking
-XX:+PrintSafepointStatistics
十、总结
synchronized的对象锁机制是Java并发设计的典范之作。从偏向锁到重量级锁的升级路径,体现了JVM开发者在性能优化上的深度思考。理解这个过程的每个细节,不仅有助于我们编写更高效的并发代码,更能让我们体会到系统设计中的平衡智慧。
在微服务和高并发成为主流的今天,虽然出现了更多新的并发工具,但synchronized凭借其简洁性和持续的优化,仍然是大多数场景下的首选同步机制。



「在线考试系统源码(含搭建教程)」 (无删减,无套路):🔥🔥🔥
链接:https://pan.quark.cn/s/96c4f00fdb43 提取码:WR6M
往期免费源码对应视频:
免费获取--SpringBoot+Vue宠物商城网站系统
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我
💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!💖常来我家多看看,
📕网址:扣棣编程,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!
⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇
2864

被折叠的 条评论
为什么被折叠?



