synchronized 锁升级

简介: JDK 6 引入的 synchronized 锁升级机制,通过偏向锁、轻量级锁和重量级锁的动态切换,优化了多线程同步性能。该机制根据竞争情况逐步升级锁状态,减少线程阻塞和系统调用开销,从而提升并发效率。

synchronized 锁升级是 JDK 6 引入的重要优化,通过偏向锁、轻量级锁、重量级锁的逐步升级策略,减少了传统重量级锁的性能开销。以下是锁升级的详细过程和原理:

1. 锁升级的核心动机

  • 传统重量级锁的问题:依赖操作系统的互斥量(Mutex)实现,线程阻塞和唤醒需从用户态切换到内核态,性能损耗大。
  • 优化思路:根据实际场景动态调整锁的粒度,避免不必要的重量级锁开销。

2. 锁状态与对象头

每个 Java 对象的对象头(Mark Word)中存储着锁状态信息,不同状态下 Mark Word 的结构如下:

锁状态 存储内容 标志位
无锁 对象哈希码、分代年龄 01
偏向锁 线程 ID、Epoch、分代年龄 01
轻量级锁 指向线程栈中锁记录的指针 00
重量级锁 指向 Monitor 的指针 10
GC 标记 11

3. 锁升级的具体过程

(1)偏向锁(Biased Locking)

  • 适用场景:只有一个线程访问同步块,无竞争。
  • 获取过程
    1. 当第一个线程 T1 访问同步块时,JVM 通过 CAS 操作将线程 ID 写入 Mark Word,同时将标志位设为 01(偏向锁)。
    2. 后续 T1 再次进入同步块时,无需任何同步操作,直接判断 Mark Word 中的线程 ID 是否为自己,若是则直接执行。
  • 撤销过程
    1. 当有其他线程 T2 尝试竞争该锁时,T1 会被暂停,JVM 检查 T1 是否仍在执行同步块:
      • 若已退出:Mark Word 恢复为无锁状态,T2 可竞争并获取偏向锁。
      • 若仍在执行:偏向锁升级为轻量级锁,T1 继续执行,T2 自旋等待。
  • 批量重偏向与撤销
    • 当一个类的对象频繁发生偏向锁撤销时,JVM 会认为该类不适合偏向锁,批量将该类的对象偏向锁撤销或禁用。

(2)轻量级锁(Lightweight Lock)

  • 适用场景:多个线程交替访问同步块,无实际竞争。
  • 加锁过程
    1. 线程进入同步块前,在当前线程的栈帧中创建锁记录(Lock Record),并将 Mark Word 复制到锁记录中(Displaced Mark Word)。
    2. 通过 CAS 尝试将 Mark Word 更新为指向锁记录的指针:
      • 成功:获取轻量级锁,Mark Word 标志位变为 00
      • 失败:表示有其他线程竞争,锁升级为重量级锁。
  • 解锁过程
    1. 通过 CAS 将锁记录中的 Displaced Mark Word 替换回 Mark Word。
    2. 若替换成功:锁释放完成。
    3. 若替换失败:表示有其他线程在竞争,已升级为重量级锁,需唤醒被阻塞的线程。

(3)重量级锁(Heavyweight Lock)

  • 适用场景:多个线程同时竞争锁。
  • 升级过程
    1. 当轻量级锁竞争失败时,锁升级为重量级锁,Mark Word 存储指向 Monitor 的指针,标志位变为 10
    2. 未获取到锁的线程会被阻塞(进入 Monitor 的 EntryList),释放 CPU 资源。
  • 释放过程
    1. 持有锁的线程执行完同步块后,释放 Monitor。
    2. 唤醒 EntryList 中的线程重新竞争。

4. 锁升级的流程图

无锁状态 → 偏向锁(单线程) → 轻量级锁(多线程交替) → 重量级锁(多线程竞争)

5. 锁升级的关键点

  • 不可逆性:锁只能升级,不能降级(但偏向锁可撤销为无锁)。
  • 自旋锁(Spin Lock):轻量级锁竞争时,线程会短暂自旋等待锁释放,避免直接阻塞(JDK 6 后自旋次数可自适应)。
  • 锁粗化(Lock Coarsening):JVM 会将多个连续的加锁、解锁操作合并为一个,减少锁的获取和释放次数。
  • 锁消除(Lock Elimination):对不可能存在共享资源竞争的锁进行消除(如局部变量锁)。

6. 性能对比

锁类型 优点 缺点 适用场景
偏向锁 无同步开销,单线程性能极佳 存在锁撤销开销 单线程访问同步块
轻量级锁 竞争不激烈时避免线程阻塞 自旋消耗 CPU 多线程交替访问
重量级锁 保证线程安全,处理高竞争场景 线程阻塞和唤醒开销大 多线程同时竞争

7. 优化建议

  • 减少锁竞争:通过减小锁粒度、缩短锁持有时间降低竞争。
  • 批量偏向锁:对于频繁创建的对象,可通过 JVM 参数 -XX:+UseBiasedLocking 启用偏向锁。
  • 高并发场景:考虑使用 ReentrantLockConcurrentHashMap 等更灵活的同步工具。

总结

锁升级机制通过动态调整锁的状态,在不同场景下平衡了性能和线程安全:

  • 偏向锁:优化单线程场景,几乎无同步开销。
  • 轻量级锁:处理多线程交替访问,避免内核态切换。
  • 重量级锁:应对高竞争场景,保证线程安全。

理解锁升级原理有助于写出更高效的多线程代码,避免不必要的性能损耗。

目录
相关文章
|
Java
G1垃圾回收器的工作流程
G1垃圾回收器的工作流程
2195 0
|
存储 缓存 监控
美团面试:说说OOM三大场景和解决方案? (绝对史上最全)
小伙伴们,有没有遇到过程序突然崩溃,然后抛出一个OutOfMemoryError的异常?这就是我们俗称的OOM,也就是内存溢出 本文来带大家学习Java OOM的三大经典场景以及解决方案,保证让你有所收获!
5839 0
美团面试:说说OOM三大场景和解决方案? (绝对史上最全)
|
缓存 Java 应用服务中间件
Tomcat是如何打破"双亲委派"机制的?
上文我们详细了解了类加载以及什么是双亲委派机制,相信很多童鞋都了解Tomcat打破了双亲委派机制,本文将对Tomcat为什么要打破双亲委派机制,以及Tomcat是如何打破双亲委派机制的,进行完整性的复盘与解析。
3751 0
Tomcat是如何打破"双亲委派"机制的?
|
12月前
|
消息中间件 中间件 Kafka
分布式事务最全详解 ,看这篇就够了!
本文详解分布式事务的一致性及实战解决方案,包括CAP理论、BASE理论及2PC、TCC、消息队列等常见方案,助你深入理解分布式系统的核心技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式事务最全详解 ,看这篇就够了!
|
8月前
|
NoSQL Java Redis
springboot怎么使用Redisson
通过以上步骤,已经详细介绍了如何在Spring Boot项目中使用Redisson,包括添加依赖、配置Redisson、创建配置类以及使用Redisson实现分布式锁和分布式集合。Redisson提供了丰富的分布式数据结构和工具,可以帮助开发者更高效地实现分布式系统。通过合理使用这些工具,可以显著提高系统的性能和可靠性。
2615 34
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
10月前
|
消息中间件 存储 缓存
一文带你秒懂 Kafka工作原理!
Apache Kafka 是一个高吞吐量、低延迟的分布式消息系统,广泛应用于实时数据处理、日志收集和消息队列等领域。它最初由LinkedIn开发,2011年成为Apache项目。Kafka支持消息的发布与订阅,具备高效的消息持久化能力,适用于TB级数据的处理。
|
10月前
|
关系型数据库 MySQL 中间件
MySQL 中如何实现分库分表?常见的分库分表策略有哪些?
在MySQL中,分库分表(Sharding)通过将数据分散到多个数据库或表中,以应对大量数据带来的性能和扩展性问题。常见策略包括:哈希分片(分布均匀,查询效率高)、范围分片(适合范围查询)、列表分片(适用于特定值查询)、复合分片(灵活性高)和动态分片(灵活应对负载变化)。每种策略各有优劣,需根据业务需求选择。常用工具如MyCAT、ShardingSphere和TDDL可简化实现过程。
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
1948 2