JVM 锁自动升级机制详解

JVM 锁自动升级机制详解

JVM 中的锁升级是指 synchronized 锁从无锁状态逐步升级到重量级锁的过程,这是 Java 并发性能优化的核心机制之一。Java 8 的 ConcurrentHashMap 使用的 synchronized 正是受益于这套自动升级机制。

1. 锁升级的四个阶段

JVM 锁状态按照竞争程度从低到高分为:

  1. 无锁状态 (Unlocked)

  2. 偏向锁 (Biased Locking)

  3. 轻量级锁 (Lightweight Locking)

  4. 重量级锁 (Heavyweight Locking)

    [无锁] → [偏向锁] → [轻量级锁] → [重量级锁]

2. 各阶段详细机制

2.1 偏向锁 (Biased Locking)

适用场景:没有实际竞争或只有单线程访问

实现原理

  • 在对象头 Mark Word 中记录偏向线程ID
  • 后续进入时只需检查线程ID是否匹配
  • 不涉及 CAS 操作和操作系统互斥

升级触发条件

  • 另一个线程尝试获取锁(产生竞争)

优势

  • 完全无同步开销(仅第一次有开销)
  • 适合 ConcurrentHashMap 的单桶无竞争情况

2.2 轻量级锁 (Thin Lock)

适用场景:低竞争、短时间同步

实现原理

  1. 在栈帧中建立锁记录(Lock Record)

  2. 使用 CAS 将对象头 Mark Word 替换为指向锁记录的指针

    assembly 复制代码
    cmpxchg  // CAS指令
  3. 成功则获取锁,失败则膨胀为重量级锁

升级触发条件

  • CAS 操作失败(表示有竞争)
  • 自旋超过阈值(默认10次,JVM可调节)

优势

  • 避免直接进入重量级锁
  • 在用户态完成同步(无需内核介入)

2.3 重量级锁 (Heavyweight Lock)

适用场景:高竞争场景

实现原理

  • 通过操作系统的互斥量(mutex)实现
  • 未获取锁的线程进入等待队列
  • 涉及用户态到内核态的切换

特点

  • 性能开销大(约100ns级延迟)
  • 保证公平性和可靠性

3. 锁升级全过程示例

以 ConcurrentHashMap 的桶锁为例:

java 复制代码
synchronized(bucketHead) {  // 桶头节点作为锁对象
    // 同步代码块
}
  1. 首次进入

    • 启用偏向锁,记录当前线程ID到对象头
  2. 同一线程再次进入

    • 检查线程ID匹配,直接执行(零开销)
  3. 第二个线程进入

    • 撤销偏向锁
    • 升级为轻量级锁(CAS竞争)
  4. CAS竞争失败

    • 自旋重试(自适应自旋)
    • 自旋失败后升级重量级锁

4. 关键技术实现

4.1 对象头结构

复制代码
|---------------------------------------------------|
| Mark Word (64 bits)                               |
|---------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
|---------------------------------------------------|
  • biased_lock:偏向锁标志
  • lock:锁状态标志
    • 00:轻量级锁
    • 01:无锁/偏向锁
    • 10:重量级锁
    • 11:GC标记

4.2 升级过程关键代码(HotSpot 实现)

cpp 复制代码
// 偏向锁撤销
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, TRAPS) {
    if (UseBiasedLocking) {
        BiasedLocking::revoke(obj, THREAD);
    }
    slow_enter(obj, lock, THREAD);
}

// 轻量级锁获取
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
    markOop mark = obj->mark();
    if (mark->is_neutral()) {  // 无锁状态
        lock->set_displaced_header(mark);
        if (mark == obj()->cas_set_mark(markOopDesc::INFLATING(), mark)) {
            // 膨胀为重量级锁
            ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
        }
    }
}

5. 性能影响与优化

5.1 各阶段典型耗时

锁阶段 耗时 特点
偏向锁 ~1-2ns 仅第一次有开销
轻量级锁 ~5-10ns CAS操作开销
重量级锁 ~100ns+ 系统调用/上下文切换

5.2 JVM 调优参数

  1. 关闭偏向锁(高竞争场景):

    bash 复制代码
    -XX:-UseBiasedLocking
  2. 调整自旋次数:

    bash 复制代码
    -XX:PreBlockSpin=10
  3. 启用自适应性自旋:

    bash 复制代码
    -XX:+UseSpinning

6. 在 ConcurrentHashMap 中的应用

  1. 理想情况

    • 多数桶无竞争 → 保持偏向锁状态
    • 读操作完全无锁
  2. 中等竞争

    • 少量线程操作同一桶 → 轻量级锁
    • 通过CAS快速解决冲突
  3. 高竞争

    • 热点桶出现 → 升级重量级锁
    • 但仅限于单个桶不影响其他桶操作

7. 现代JVM的优化趋势

  1. 锁消除 (Lock Elision):

    • 逃逸分析确定对象不会逃逸时,直接消除锁
  2. 锁粗化 (Lock Coarsening):

    • 合并相邻的同步块减少锁开销
  3. 自适应自旋

    • 根据历史成功率动态调整自旋时间

这种自动升级机制使得:

  • 无竞争时达到近乎无锁的性能
  • 低竞争时保持高效
  • 高竞争时保证正确性
  • 完美适配 ConcurrentHashMap 的桶锁需求
相关推荐
teeeeeeemo26 分钟前
Ajax、Axios、Fetch核心区别
开发语言·前端·javascript·笔记·ajax
Gu_shiwww2 小时前
数据结构2线性表——顺序表
c语言·开发语言·数据结构·python
要做朋鱼燕2 小时前
理清C语言中内存操作的函数
c语言·开发语言
昏睡红猹2 小时前
记一次C#平台调用中因非托管union类型导致的内存访问越界
c#
Volunteer Technology3 小时前
Lua基础+Lua数据类型
开发语言·junit·lua
在打豆豆的小潘学长4 小时前
【R语言】多样本单细胞分析_SCTransform+Harmony方案(2)
开发语言·r语言
etcix5 小时前
urmom damn the jvm
jvm
huluang5 小时前
Word XML 批注范围克隆处理器
开发语言·c#
C4程序员6 小时前
北京JAVA基础面试30天打卡06
java·开发语言·面试
teeeeeeemo6 小时前
一些js数组去重的实现算法
开发语言·前端·javascript·笔记·算法