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 等更灵活的同步工具。

总结

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

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

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

目录
相关文章
|
6月前
|
安全
一文搞懂synchronized锁的升级过程
synchronized锁的升级过程包括偏向锁、轻量锁和重量级锁。偏向锁在无竞争时可重复使用,轻量锁通过CAS自旋实现多线程竞争,重量级锁则会导致线程阻塞,涉及用户态到内核态的切换。CAS(比较并交换)用于实现乐观锁,保证原子性操作,但可能引发CPU资源浪费。文中还展示了手写锁的升级实现代码。
384 0
|
Java
G1垃圾回收器的工作流程
G1垃圾回收器的工作流程
2378 0
|
存储 缓存 监控
美团面试:说说OOM三大场景和解决方案? (绝对史上最全)
小伙伴们,有没有遇到过程序突然崩溃,然后抛出一个OutOfMemoryError的异常?这就是我们俗称的OOM,也就是内存溢出 本文来带大家学习Java OOM的三大经典场景以及解决方案,保证让你有所收获!
6688 2
美团面试:说说OOM三大场景和解决方案? (绝对史上最全)
|
缓存 Java 应用服务中间件
Tomcat是如何打破"双亲委派"机制的?
上文我们详细了解了类加载以及什么是双亲委派机制,相信很多童鞋都了解Tomcat打破了双亲委派机制,本文将对Tomcat为什么要打破双亲委派机制,以及Tomcat是如何打破双亲委派机制的,进行完整性的复盘与解析。
4019 0
Tomcat是如何打破"双亲委派"机制的?
|
2月前
|
算法 NoSQL Java
拒绝服务雪崩!4种经典限流算法图文详解(附Java实战代码)
限流是保护系统的“保险丝”,防止突发流量导致服务雪崩。常见算法有:固定窗口(简单但有突刺)、滑动窗口(精准平滑)、漏桶(恒定处理速率)和令牌桶(允许突发,最常用)。单机限流可用计数器或Guava,分布式场景则依赖Redis实现全局控制。
611 9
|
9月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
310 2
|
8月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 > Java系统属性 > application.properties > application.yml > application.yaml。
1203 0
|
8月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
592 0
|
5月前
|
消息中间件 监控 Kubernetes
别再乱排查了!Kafka 消息积压、重复、丢失,根源基本都是 Rebalance!
大家好,我是小富~分享一次Kafka消息积压排查经历:消费者组因Rebalance导致消费能力骤降。本文详解Rebalance触发场景(消费者变更、分区扩容、订阅变化、超时等),剖析其引发的消息积压、重复消费、丢失等问题根源,并提供优化方案:调优超时参数、手动提交offset、启用粘性分配策略、保障消费幂等性。掌握这些,轻松应对Kafka常见故障!
1122 0
|
11月前
|
存储 安全 Java
ThreadLocal - 原理与应用场景详解
ThreadLocal是Java中用于实现线程隔离的重要工具,为每个线程提供独立的变量副本,避免多线程数据共享带来的安全问题。其核心原理是通过 ThreadLocalMap 实现键值对存储,每个线程维护自己的存储空间。ThreadLocal 广泛应用于线程隔离、跨层数据传递、复杂调用链路的全局参数传递及数据库连接管理等场景。此外,InheritableThreadLocal 支持子线程继承父线程的变量值,而 TransmittableThreadLocal 则解决了线程池中变量传递的问题,提升了多线程上下文管理的可靠性。深入理解这些机制,有助于开发者更好地解决多线程环境下的数据隔离与共享挑战。
1977 44

热门文章

最新文章