一、加锁流程
synchronized
的加锁过程涉及 锁升级机制(偏向锁 → 轻量级锁 → 重量级锁),JVM 根据竞争情况动态调整锁状态以优化性能。以下是详细步骤:
1. 无锁状态 → 偏向锁
-
场景:首次线程访问同步块,无竞争。
-
流程:
- 检查对象头 :确认当前为无锁状态(锁标志位
01
)。 - CAS 设置偏向线程 :将对象头中的
Mark Word
替换为当前线程 ID 和偏向时间戳(通过 CAS 操作)。 - 进入同步块:后续同一线程重入时,仅需检查线程 ID 是否匹配,无需额外操作。
- 检查对象头 :确认当前为无锁状态(锁标志位
-
优点:无竞争时完全无同步开销。
2. 偏向锁 → 轻量级锁
-
触发条件:其他线程尝试获取锁,触发偏向锁撤销。
-
流程:
-
暂停持有偏向锁的线程:JVM 通过安全点(Safe Point)暂停线程。
-
撤销偏向锁:将对象头恢复为无锁状态,并记录锁撤销信息。
-
升级为轻量级锁:
- 线程在栈帧中创建锁记录(Lock Record),存储对象头的
Mark Word
副本。 - 通过 CAS 将对象头的
Mark Word
替换为指向锁记录的指针。 - 若成功,线程获得轻量级锁;若失败,进入自旋等待。
- 线程在栈帧中创建锁记录(Lock Record),存储对象头的
-
-
优点:竞争较小时通过 CAS 避免线程阻塞。
3. 轻量级锁 → 重量级锁
-
触发条件:自旋超过阈值(默认 10 次)或竞争加剧。
-
流程:
- 膨胀为重量级锁 :JVM 创建
Monitor
对象(C++ 的ObjectMonitor
),并将对象头指向Monitor
。 - 线程进入阻塞队列 :竞争失败的线程进入
Monitor
的_EntryList
队列,等待操作系统调度。 - 切换至内核态:依赖操作系统的互斥量(Mutex Lock)实现线程阻塞。
- 膨胀为重量级锁 :JVM 创建
-
缺点:上下文切换开销大,性能下降。
二、解锁流程
解锁过程需处理不同锁状态,并唤醒等待线程:
1. 偏向锁解锁
-
流程:
- 检查线程 ID:确认当前线程为锁持有者。
- 直接释放:无需修改对象头,保留偏向状态。
-
特点:无竞争时解锁无开销。
2. 轻量级锁解锁
-
流程:
- CAS 恢复对象头 :将锁记录中的
Mark Word
副本通过 CAS 写回对象头。 - 成功:对象恢复为无锁状态。
- 失败 :说明锁已膨胀为重量级锁,唤醒
Monitor
中的等待线程。
- CAS 恢复对象头 :将锁记录中的
-
特点:依赖 CAS 操作,无线程唤醒开销。
3. 重量级锁解锁
-
流程:
- 释放 Monitor :将
Monitor
的_owner
字段置为null
。 - 唤醒等待线程 :从
_EntryList
或_WaitSet
队列中唤醒一个线程。 - 上下文切换:操作系统调度唤醒的线程重新竞争锁。
- 释放 Monitor :将
-
特点:涉及内核态切换,开销较大。
三、内存语义与屏障
-
加锁时:
- Load 屏障:强制从主内存加载共享变量最新值。
- Acquire 屏障:禁止同步块内读操作与块外指令重排序。
-
解锁时:
- Release 屏障:强制将工作内存的修改刷新到主内存。
- Store 屏障:禁止同步块内写操作与块外指令重排序。
四、流程图解
scss
加锁流程:
[无锁] → (首次线程访问) → [偏向锁] → (竞争发生) → [轻量级锁] → (自旋失败) → [重量级锁]
↑_________________________↓ ↑_______________↓
解锁流程:
[偏向锁] → 无操作
[轻量级锁] → CAS 恢复对象头
[重量级锁] → 释放 Monitor,唤醒线程
五、性能优化策略
-
减少锁竞争:
- 缩小同步范围(如细化锁粒度)。
- 使用无锁数据结构(如
ConcurrentHashMap
)。
-
避免锁升级:
- 控制同步块执行时间(减少自旋失败概率)。
-
锁消除(Lock Elision) :
- JIT 编译器优化,去除不可能竞争的锁(如局部变量锁)。
-
锁粗化(Lock Coarsening) :
- 合并相邻的同步块,减少锁获取/释放次数。
六、总结
阶段 | 触发条件 | 性能开销 | 适用场景 |
---|---|---|---|
偏向锁 | 单线程重复访问 | 极低 | 无竞争环境 |
轻量级锁 | 低并发竞争 | 低(CAS) | 短时间同步块 |
重量级锁 | 高并发竞争 | 高(阻塞) | 长时间同步块或高并发 |
关键点:
- 偏向锁 和 轻量级锁 是 JVM 的优化手段,旨在减少无竞争或低竞争时的开销。
- 重量级锁 是最终兜底方案,依赖操作系统实现线程阻塞与唤醒。
- 理解锁升级流程有助于编写高效并发代码,避免不必要的性能损耗。