当前位置: 首页 > news >正文

深入理解synchronized:从使用到原理的进阶指南

目录

一、核心机制深度解析

1. 对象头(Object Header)与Mark Word的奥秘

2. Monitor:同步的实质

二、锁升级的全过程与底层操作

1. 无锁 -> 偏向锁

2. 偏向锁 -> 轻量级锁

3. 轻量级锁 -> 重量级锁

三、高级话题与实战调优

1. 锁的优劣对比

2. 锁降级

        3. synchronized的性能问题


作为Java并发编程的基石,synchronized 关键字的重要性不言而喻。它不仅仅是一个关键字,更是一套完整的线程同步解决方案,其背后蕴含着Java虚拟机精湛的设计哲学。本文将带领你从字节码层面到操作系统内核,全方位剖析 synchronized 的实现原理、优化手段与实战技巧。

一、核心机制深度解析

synchronized 的实现建立在两个核心概念之上:对象头(Object Header) 和 Monitor(监视器)。理解这两者是掌握 synchronized 的关键。

1. 对象头(Object Header)与Mark Word的奥秘

每个Java对象在堆内存中的存储布局都包含以下部分:

  • 对象头 (Object Header):包含两类信息:

    • Mark Word:这是实现锁的核心。在64位JVM中,它通常占64位(8字节),是一个动态变化的数据结构,其内容会根据锁的状态而发生改变。它用于存储对象的哈希码(hashCode)、GC分代年龄、锁状态标志、线程持有的锁信息、偏向线程ID等。

    • Klass Pointer:指向对象的类元数据(Class对象)的指针,JVM通过它来确定对象属于哪个类。在64位JVM中,默认开启指针压缩(-XX:+UseCompressedOops),此指针被压缩为32位。

  • 实例数据 (Instance Data):对象真正存储的有效信息,即程序代码中定义的各种字段内容。

  • 对齐填充 (Padding):起占位符作用,HotSpot VM要求对象起始地址必须是8字节的整数倍,因此需要对对象大小进行对齐填充。

Mark Word在不同锁状态下的结构是其精髓所在,如下图所示:
(此处应有一张Mark Word内存布局图,展示无锁、偏向锁、轻量级锁、重量级锁、GC标记状态下的位分布)

锁状态偏向锁标志 (biased_lock)锁标志位 (lock)存储内容
无锁001对象的哈希码(hashCode)、对象分代年龄
偏向锁101持有偏向锁的线程ID、epoch、对象分代年龄
轻量级锁-00指向栈中锁记录(Lock Record)的指针
重量级锁-10指向操作系统互斥量(Mutex)和等待队列的指针
GC标记-11空(表示该对象正被垃圾回收)

2. Monitor:同步的实质

每个Java对象都可以关联一个Monitor(监视器锁)。在HotSpot虚拟机中,Monitor是由 ObjectMonitor 类(C++实现)实现的,其主要数据结构包括:

  • _owner:指向当前持有该Monitor的线程。

  • _EntryList:存储所有阻塞等待获取该Monitor的线程。

  • _WaitSet:存储调用了 Object.wait() 方法而进入等待状态的线程。

当线程执行到 synchronized 保护的代码块时:

  1. 执行 monitorenter 指令,尝试通过CAS操作将Monitor的 _owner 字段设置为当前线程。

  2. 如果成功,该线程即持有Monitor,进入临界区执行代码。

  3. 如果失败,说明Monitor已被其他线程持有,当前线程会进入 _EntryList 队列中阻塞等待。

  4. 持有Monitor的线程执行完同步代码后,会执行 monitorexit 指令,将 _owner 置为 null 并唤醒 _EntryList 中的线程,它们将重新竞争锁。

二、锁升级的全过程与底层操作

锁升级是 synchronized 性能优化的核心,其目的是在无竞争或低竞争情况下,避免使用重量级锁带来的高昂开销。

1. 无锁 -> 偏向锁

  • 触发条件:第一个线程访问同步块。

  • 底层操作:JVM使用CAS操作,将当前线程ID写入对象头的Mark Word中,并设置偏向锁标志。如果成功,则线程成功获取偏向锁。

  • 优势:此后该线程再进入同步块时,只需检查Mark Word中的线程ID是否为自己,是则直接执行,无需任何同步操作,开销极小。

2. 偏向锁 -> 轻量级锁

  • 触发条件:有另一个线程来竞争锁(偏向锁发生撤销)。

  • 底层操作

    1. 首先,JVM会暂停拥有偏向锁的线程。

    2. 然后,在该线程的栈帧中创建一个锁记录(Lock Record) 空间。

    3. 将对象当前的Mark Word复制到该线程的锁记录中(称为Displaced Mark Word)。

    4. 线程使用CAS操作尝试将对象头的Mark Word替换为指向其锁记录的指针。

    5. 如果成功,当前线程获得轻量级锁;如果失败,表示存在竞争,进而升级为重量级锁

  • 优势:在没有真正竞争的情况下,使用CAS这种用户态操作避免了操作系统内核态切换的开销。

3. 轻量级锁 -> 重量级锁

  • 触发条件:轻量级锁的CAS操作失败(自旋后仍无法获取锁)。

  • 底层操作

    1. 当前线程会先自旋一小段时间(自适应自旋),尝试获取锁,以避免直接升级带来的开销。

    2. 如果自旋后仍无法获取,锁正式升级为重量级锁。

    3. JVM会向操作系统申请一个互斥量(Mutex),并将对象头的Mark Word更新为指向该互斥量的指针。

    4. 未能获取锁的线程会被挂起(park),进入 _EntryList 队列,等待操作系统的调度唤醒。

  • 开销:线程的挂起和唤醒涉及用户态到内核态的切换,上下文切换成本非常高。

三、高级话题与实战调优

1. 锁的优劣对比

锁类型优点缺点适用场景
偏向锁加锁解锁无额外开销锁撤销有额外开销单线程访问同步块
轻量级锁竞争线程不阻塞,程序响应快长时间自旋会消耗CPU追求响应时间,同步块执行快
重量级锁竞争线程不自旋,不消耗CPU线程阻塞,响应慢追求吞吐量,同步块执行时间长

2. 锁降级

锁降级确实存在,但其场景非常有限,主要发生在 GC的STW阶段。为了减少GC停顿时间,JVM会尝试进行锁降级。但在正常的用户代码执行路径中,锁升级是不可逆的,这是为了避免在重量级锁和轻量级锁之间反复切换带来的巨大性能损耗。

3. 性能调优最佳实践

  • 减少锁粒度:缩小同步代码块的范围,最经典的例子是 ConcurrentHashMap 使用分段锁。

  • 减少锁持有时间:避免在同步块内执行耗时的I/O操作、网络请求或复杂计算。

  • 读写分离:使用 ReadWriteLock 替代独占锁,允许读读并发,提高读多写少场景的性能。

  • JVM参数调优

    • -XX:-UseBiasedLocking:明确知道会有高竞争时,可禁用偏向锁

    • -XX:BiasedLockingStartupDelay=0:取消偏向锁延迟(默认4s),适用于启动后立即高并发的应用。

    • -XX:+UseSpinning / -XX:PreBlockSpin:控制轻量级锁的自旋策略(JDK6之后是自适应自旋,通常无需手动调整)。

        3. synchronized的性能问题

  1. 锁粒度太大:同步范围覆盖过多无关代码,导致线程竞争加剧。
  2. 锁持有时间过长:同步块中包含耗时操作。
  3. 锁竞争激烈:多个线程频繁争抢同一把锁,导致上下文切换频繁。
  4. 锁升级频繁:大量竞争导致锁从偏向锁升级到重量级锁。
  5. 死锁:线程互相等待对方释放锁,导致系统停滞。


文章转载自:

http://am9ztfTp.pzrnf.cn
http://npVgkZsT.pzrnf.cn
http://50jBuLMS.pzrnf.cn
http://b8RaCyYA.pzrnf.cn
http://fYOKYCwc.pzrnf.cn
http://qK0xNzlG.pzrnf.cn
http://72LPoOJZ.pzrnf.cn
http://Bo7mLFAp.pzrnf.cn
http://imva0Y9b.pzrnf.cn
http://2QCNk3P2.pzrnf.cn
http://PCZI2QBm.pzrnf.cn
http://w45nqVUp.pzrnf.cn
http://5HJyNN5m.pzrnf.cn
http://rTumY1x3.pzrnf.cn
http://l3yWc96i.pzrnf.cn
http://fSnmrSSE.pzrnf.cn
http://sUcYqBxr.pzrnf.cn
http://X4eZDEgS.pzrnf.cn
http://Xzidx8Kd.pzrnf.cn
http://KnFhcffo.pzrnf.cn
http://Elro8lpZ.pzrnf.cn
http://JQCPWjj1.pzrnf.cn
http://GVaDe6yj.pzrnf.cn
http://gYwriihf.pzrnf.cn
http://fAAdjk4N.pzrnf.cn
http://E2FwcyVh.pzrnf.cn
http://cU6kWWN6.pzrnf.cn
http://jY9D6bBr.pzrnf.cn
http://yUNbLpjs.pzrnf.cn
http://LHFNtkAA.pzrnf.cn
http://www.dtcms.com/a/375552.html

相关文章:

  • 第八章 矩阵按键实验
  • 【CSS 3D 实战】从零实现旋转立方体:理解 3D 空间的核心原理
  • C++互斥锁使用详解与案例分析
  • Python+DRVT 从外部调用 Revit:批量创建柱
  • Matlab机器人工具箱6.2 导入stl模型——用urdf文件描述
  • 网页设计模板 HTML源码网站模板下载
  • 南京大学计算机学院 智能软件工程导论 + Luciano Baresi 教授讲座
  • Rust/C/C++ 混合构建 - Buck2构建工具一探究竟
  • Drawnix:开源一体化白板工具,让你的创意无限流动!
  • stm32 链接脚本没有 .gcc_except_table 段也能支持 C++ 异常
  • K8S集群管理(4)
  • flutter TabBar 设置isScrollable 第一个有间距
  • 学习 Android (二十一) 学习 OpenCV (六)
  • Maven项目中修改公共依赖项目并发布到nexus供三方引用全流程示例
  • GD32VW553-IOT开发板移植适配openharmony
  • nuxt3在使用vue-echarts报错 document is not defined
  • 嵌入式第四十九天(ARM汇编指令)
  • RS485通信 , 和modus RTU
  • 7. LangChain4j + 记忆缓存详细说明
  • 【超简单】Anaconda 安装教程(Windows 图文版)
  • Docker 搭建 Harbor 镜像仓库
  • 数据采集平台的起源与演进:从ETL到数据复制
  • Blender 制作中世纪风格的水磨坊(2):场景元素、纹理与渲染后期
  • 【Python】pytorch安装(使用conda)
  • 阿里云centos7-mysql的使用
  • Android实战进阶 - 启动页
  • 【从零开始编写数据库系统】基于Python语言实现存储引擎
  • 【Pywinauto库】8.3 pywinauto.findwindows 模块
  • 351章:Python Web爬虫入门:使用Requests和BeautifulSoup
  • 禅道,用域名访问之后不能登录的问题