Java中的锁升级过程

Java 中的锁升级是 JVM 在 JDK 1.6 后对 synchronized 关键字的优化机制,​​根据线程竞争强度动态调整锁状态​​,从低开销的偏向锁逐步升级到高开销的重量级锁,以平衡性能与线程安全。下面结合原理、示例和对象头变化详细说明锁升级过程:

​​​​​​​


🔒 一、锁升级的四个阶段及触发条件

锁升级路径固定且不可逆:
​无锁 → 偏向锁 → 轻量级锁 → 重量级锁​

1. ​​无锁状态(No Lock)​
  • ​场景​​:对象刚创建,未被任何线程加锁。
  • ​对象头 Mark Word​​:存储对象的 HashCode、分代年龄等,锁标志位 01(最低两位)。
  • ​示例​​:
    Object obj = new Object(); // 此时处于无锁状态
2. ​​偏向锁(Biased Lock)​
  • ​触发条件​​:第一个线程访问同步块。
  • ​优化目标​​:消除无竞争时的同步开销(如单线程重复加锁)。
  • ​实现​​:
    • 对象头记录​​偏向线程 ID​​ 和​​偏向时间戳(epoch)​​,锁标志位变为 101
    • 同一线程再次进入同步块时,只需检查线程 ID 是否匹配,无需 CAS 操作。
  • ​升级条件​​:第二个线程尝试获取锁(发生竞争)。
  • ​示例​​:
    synchronized (lock) {
        // 线程 A 首次访问:启用偏向锁,记录线程 ID
    }
3. ​​轻量级锁(Lightweight Lock)​
  • ​触发条件​​:偏向锁被撤销(有竞争发生)。
  • ​优化目标​​:避免线程阻塞,通过 ​​CAS 自旋​​减少上下文切换。
  • ​实现​​:
    • 线程在栈帧中创建 ​​Lock Record​​,通过 CAS 将对象头替换为指向 Lock Record 的指针,锁标志位变为 00
    • 若 CAS 失败,线程开始​​自适应自旋​​(JVM 根据历史成功率动态调整自旋次数)。
  • ​升级条件​​:自旋超过阈值(默认 10 次)或竞争线程数增加(如第三个线程加入)。
  • ​示例​​:
    // 线程 B 尝试获取已被线程 A 偏向的锁
    synchronized (lock) { 
        // 偏向锁撤销,升级为轻量级锁,线程 B 通过自旋尝试 CAS
    }
4. ​​重量级锁(Heavyweight Lock)​
  • ​触发条件​​:轻量级锁自旋失败。
  • ​实现​​:
    • 对象头指向 ​​Monitor 对象​​(操作系统互斥量 Mutex),锁标志位变为 10
    • 未获取锁的线程进入阻塞队列,由操作系统调度(涉及用户态到内核态切换,开销大)。
  • ​特点​​:保证高竞争下的公平性,但线程阻塞和唤醒成本高。
  • ​示例​​:
    // 多个线程(如 10 个)同时竞争锁
    synchronized (lock) {
        // 轻量级锁自旋失败,升级为重量级锁,线程进入阻塞状态
    }

⚙️ 二、完整锁升级示例及对象头变化

代码演示
public class LockUpgradeDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 阶段 1:偏向锁(线程 A 首次访问)
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread1: 获取偏向锁");
            }
        }).start();
        Thread.sleep(1000); // 确保 Thread1 执行完

        // 阶段 2:轻量级锁(线程 B 竞争)
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread2: 升级为轻量级锁");
            }
        }).start();
        Thread.sleep(500);

        // 阶段 3:重量级锁(多个线程高竞争)
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    System.out.println("Thread" + i + ": 升级为重量级锁");
                }
            }).start();
        }
    }
}
对象头 Mark Word 变化(使用 JOL 工具观察)
// 添加依赖:org.openjdk.jol:jol-core
public static void main(String[] args) {
    Object obj = new Object();
    System.out.println("无锁状态:\n" + ClassLayout.parseInstance(obj).toPrintable());

    synchronized (obj) {
        System.out.println("偏向锁/轻量级锁:\n" + ClassLayout.parseInstance(obj).toPrintable());
    }

    // 高竞争下观察重量级锁
    new Thread(() -> {
        synchronized (obj) {
            System.out.println("重量级锁:\n" + ClassLayout.parseInstance(obj).toPrintable());
        }
    }).start();
}

​输出说明​​:

  • 无锁:Mark Word 末尾标志位 01
  • 偏向锁:Mark Word 含线程 ID,标志位 101
  • 轻量级锁:Mark Word 指向栈帧 Lock Record,标志位 00
  • 重量级锁:Mark Word 指向 Monitor,标志位 10​​​​​​​


⚠️ 三、关键注意事项

  1. ​锁升级不可逆​​:
    重量级锁不会降级(即使竞争消失),因为降级开销大于直接释放锁。

  2. ​偏向锁的延迟与禁用​​:

    • 默认 JVM 启动后 4 秒才启用偏向锁(避免初始化竞争)。
    • 可通过 -XX:BiasedLockingStartupDelay=0 立即启用,或 -XX:-UseBiasedLocking 完全禁用(高并发场景推荐)。
  3. ​优化建议​​:

    • ​减少临界区长度​​:避免长时间持锁(如 I/O 操作),降低竞争概率。
    • ​分段锁​​:如 ConcurrentHashMap 将大锁拆分为多个小锁。
    • ​避免过早膨胀​​:确保锁竞争强度与锁级别匹配(如避免在低竞争时误用重量级锁)。

💎 四、总结

  • ​核心价值​​:锁升级通过动态适配竞争强度(无竞争→低竞争→高竞争),在保证线程安全的同时最大化性能:
    • 偏向锁:单线程重复访问(无竞争)
    • 轻量级锁:短时间低竞争(CAS 自旋)
    • 重量级锁:高竞争(阻塞队列保证公平性)
  • ​开发者角色​​:无需手动干预锁升级,但需​​合理设计同步块​​(控制粒度、时长)并​​监控竞争情况​​(如通过 jstack 分析锁状态)。
  • ​性能权衡​​:偏向锁撤销可能导致 STW,高竞争下重量级锁虽开销大,但可避免 CPU 空转。

通过理解锁升级机制,开发者能更好地定位并发瓶颈(如频繁锁升级导致的阻塞),并优化同步策略(如改用 ReentrantLock 细粒度控制)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值