一、对象内存结构与锁状态标识
Java对象在堆内存中的存储结构是理解synchronized机制的基础。每个对象实例都包含一个对象头(Object Header),其内部结构在64位JVM中由两部分组成:Mark Word(标记字段)和Klass Pointer(类型指针)。前者存储对象的运行时元数据,后者指向方法区中的类元信息。
1.1 Mark Word动态结构
Mark Word采用动态位域设计,根据对象当前锁状态复用存储空间。以64位系统为例,其结构随锁状态变化如下:
| 锁状态 | 存储内容(64位) |
|---|---|
| 无锁状态 | 25位对象哈希码 + 4位分代年龄 + 1位偏向锁标志 + 2位锁标志(01) |
| 偏向锁 | 23位偏向线程ID + 4位分代年龄 + 2位偏向时间戳 + 2位锁标志(01) |
| 轻量级锁 | 62位指向栈中Lock Record的指针 + 2位锁标志(00) |
| 重量级锁 | 62位指向Monitor对象的指针 + 2位锁标志(10) |
| GC标记状态 | 62位垃圾回收相关标记 + 2位锁标志(11) |
这种动态设计使得单个对象头在不同场景下能够承载多种元数据,通过2位锁标志位(lock_state)实现状态切换。当对象首次被线程访问时,若未发生竞争则进入偏向锁状态;竞争加剧时依次升级为轻量级锁和重量级锁。
1.2 锁状态转换机制
锁升级过程遵循渐进式原则:
- 偏向锁撤销:当其他线程尝试获取偏向锁时,通过CAS操作撤销原有偏向,升级为轻量级锁
- 轻量级锁膨胀:自旋等待超过阈值(默认10次)后,膨胀为重量级锁
- 重量级锁降级:JVM规范未强制要求,但某些实现会在独占线程释放后尝试降级
这种设计平衡了低竞争场景下的性能与高竞争场景下的可靠性。例如,在单线程应用中,偏向锁可完全避免锁竞争开销;在多线程竞争场景下,通过自旋等待减少线程阻塞切换。
二、Monitor监视器实现原理
重量级锁的核心实现依赖于操作系统级别的Monitor机制,其数据结构包含三个关键字段:
class Monitor {Thread owner; // 当前持有锁的线程WaitSet waitSet; // 调用wait()的线程队列EntryList entryList; // 等待获取锁的线程队列int recursion; // 重入次数计数器}
2.1 锁获取流程详解
线程获取锁的完整流程如下:
- 偏向锁获取:检查Mark Word是否为偏向状态且线程ID匹配,匹配则直接获取
- 轻量级锁竞争:
- 在栈帧中创建Lock Record(锁记录)
- 通过CAS将Mark Word替换为指向Lock Record的指针
- 成功则获取锁,失败则进入自旋等待
- 重量级锁膨胀:
- 创建ObjectMonitor对象并关联当前对象
- 将Mark Word替换为指向Monitor的指针
- 线程挂起并加入EntryList队列
- 操作系统调度器介入实现线程阻塞
2.2 锁释放流程解析
释放锁时执行反向操作:
- 轻量级锁释放:
- 通过CAS将Mark Word恢复为无锁状态
- 若发现存在竞争(其他线程的自旋等待),则触发锁膨胀
- 重量级锁释放:
- 唤醒EntryList中的首个线程
- 清除owner字段
- 若WaitSet非空,则按规则唤醒等待线程
三、JVM指令级实现机制
synchronized的底层实现依赖于两条关键JVM指令:
3.1 monitorenter指令
该指令执行时完成以下操作:
- 检查对象引用是否为null
- 根据当前锁状态执行不同路径:
- 无锁状态:尝试建立偏向锁或轻量级锁
- 已锁定状态:检查线程是否为owner,是则增加recursion计数
- 竞争失败时,根据锁类型进入自旋或阻塞状态
3.2 monitorexit指令
释放锁时执行:
- 减少recursion计数,归零则真正释放锁
- 触发锁状态降级检查
- 唤醒后续等待线程(如有)
四、性能优化实践建议
基于上述机制,开发者可采取以下优化策略:
- 减少锁粒度:将大对象拆分为多个独立字段,分别加锁
- 锁分段技术:对集合类使用分段锁(如ConcurrentHashMap的16个Segment)
- 读写锁分离:对读多写少场景使用ReadWriteLock
- 自旋锁优化:合理设置-XX:PreBlockSpin参数(默认10次)
- 锁消除:通过逃逸分析识别无需同步的代码块
典型案例:某电商系统通过将订单锁从对象级别改为订单ID哈希分段,使TPS从800提升至3200,同时保持99.9%的请求延迟在200ms以内。
五、常见问题诊断
当出现锁竞争问题时,可通过以下工具分析:
- jstack:查看线程堆栈中的BLOCKED状态
- jstat:监控GC和锁竞争指标
- Async Profiler:分析锁等待时间分布
- Arthas:动态监控synchronized调用
典型问题场景:
- 死锁:通过线程转储分析循环等待链
- 锁争用过高:检查是否过度同步或锁粒度太大
- 偏向锁撤销频繁:考虑禁用偏向锁(-XX:-UseBiasedLocking)
通过深入理解synchronized的底层实现机制,开发者能够编写出更高效、更可靠的并发程序。在实际开发中,建议结合具体业务场景选择合适的同步策略,并在性能关键路径上进行精细化调优。