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

【JVM深度解析】第25篇:volatile与synchronized深度原理

摘要

volatile 和 synchronized 是 Java 并发编程中最常用的两个关键字,但它们的底层原理却大不相同。volatile 通过内存屏障保证可见性和有序性(无原子性),synchronized 通过监视器锁保证原子性、可见性和有序性。本文深入解析两者的底层实现:volatile 的写屏障/读屏障、CAS 与 volatile 的关系、以及 synchronized 的锁升级机制(偏向锁 → 轻量级锁 → 重量级锁)。理解这些,你才能真正用好并发工具,写出高效安全的多线程代码。


一、volatile 深度解析

1.1 volatile 的作用

volatile 的三大作用: ┌──────────────────────────────────────────────────────────────────┐ │ │ │ 1. 可见性(Visibility) │ │ - 每次读取都从主内存读取 │ │ - 每次写入都立即刷新到主内存 │ │ │ │ 2. 有序性(Ordering) │ │ - 防止指令重排序 │ │ - 写操作前插入 StoreStore 屏障 │ │ - 读操作后插入 LoadLoad + LoadStore 屏障 │ │ │ │ 3. 无原子性 │ │ - volatile long/counter++ 仍然不是线程安全的 │ │ │ └──────────────────────────────────────────────────────────────────┘

1.2 volatile 写屏障

// volatile 写入的语义publicclassVolatileWrite{privatevolatileintvalue;publicvoidwrite(intnewValue){value=newValue;// volatile 写}}// 等价于:publicvoidwrite(intnewValue){// StoreStore 屏障 - 确保之前的 store 都刷新到主内存__asm__volatile("sfence");value=newValue;// StoreLoad 屏障 - 确保之前的 store 对后续的 load 可见__asm__volatile("mfence");}

1.3 volatile 读屏障

// volatile 读取的语义publicclassVolatileRead{privatevolatileintvalue;publicintread(){intresult=value;// volatile 读returnresult;}}// 等价于:publicintread(){// LoadLoad 屏障 - 确保之前 load 完成__asm__volatile("lfence");intresult=value;// LoadStore 屏障 - 确保 load 之后不会发生 store 重排序// (隐含在大多数架构中)returnresult;}

1.4 volatile 的使用场景

// 正确使用 volatile 的场景// 场景 1:状态标志publicclassService{privatevolatilebooleanrunning=true;publicvoidstop(){running=false;// 其他线程立即看到变化}publicvoidrun(){while(running){// 正确:volatile 保证可见性}}}// 场景 2:单例模式(双重检查)publicclassSingleton{privatestaticvolatileSingletoninstance;publicstaticSingletongetInstance(){if(instance==null){synchronized(Singleton.class){if(instance==null){instance=newSingleton();// volatile 防止重排序}}}returninstance;}}// 场景 3:发布安全不可变对象publicclassHolder{privatestaticvolatileHolderinstance;// 注意:value 本身是不可变对象publicstaticHoldergetInstance(){if(instance==null){instance=newHolder();}returninstance;}}

二、synchronized 深度解析

2.1 synchronized 的三大特性

synchronized 保证的三大事物: ┌──────────────────────────────────────────────────────────────────┐ │ │ │ 1. 原子性(Atomicity) │ │ - 同一时刻只有一个线程能持有锁 │ │ - 其他线程阻塞等待 │ │ │ │ 2. 可见性(Visibility) │ │ - unlock 前必须刷新所有共享变量到主内存 │ │ - lock 时清空工作内存,从主内存重新加载 │ │ │ │ 3. 有序性(Ordering) │ │ - 持有锁后,保证代码块内部不发生重排序 │ │ │ └──────────────────────────────────────────────────────────────────┘

2.2 锁升级机制

┌──────────────────────────────────────────────────────────────────┐ │ synchronized 锁升级过程 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 偏向锁(Biased Lock) │ │ │ │ │ │ │ │ 场景:只有一个线程反复进入同步块 │ │ │ │ 原理:记录线程 ID,下次直接进入 │ │ │ │ 开销:无(无需 CAS) │ │ │ │ 撤销:其他线程竞争 → 批量撤销 / 偏向锁撤销 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ ↓ 其他线程竞争 │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 轻量级锁(Lightweight Lock) │ │ │ │ │ │ │ │ 场景:少量线程交替进入同步块 │ │ │ │ 原理:CAS 替换 Mark Word,竞争失败自旋等待 │ │ │ │ 开销:自旋消耗 CPU │ │ │ │ 升级:自旋超过阈值 → 膨胀为重量级锁 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ ↓ 竞争激烈 │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 重量级锁(Heavyweight Lock) │ │ │ │ │ │ │ │ 场景:多线程竞争同一把锁 │ │ │ │ 原理:OS Mutex,线程阻塞挂起 │ │ │ │ 开销:上下文切换(毫秒级) │ │ │ │ 特点:不再自旋,释放时唤醒等待线程 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘

2.3 Mark Word 与锁状态

Mark Word 的 64 位结构(无锁状态): ┌──────────────────────────────────────────────────────────────────┐ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 25 bit │ 31 bit │ 1 bit │ 4 bit │ 1 bit │ │ │ │ unused │ 对象哈希码 │ GC age │ 偏向锁 │ 偏向 │ │ │ │ │ (hashcode) │ │ epoch │ 标志 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ 偏向锁状态: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 54 bit │ 2 bit │ 1 bit │ 1 bit │ │ │ │ 线程指针(Thread ID) │ epoch │ 偏向锁 │ 偏向 │ │ │ │ │ │ 类型 │ 标志 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ 轻量级锁状态: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 62 bit │ │ │ │ 指向栈中锁记录的指针 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ 重量级锁状态: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 62 bit │ │ │ │ 指向互斥量(Mutex)的指针 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘

2.4 synchronized 实战

// synchronized 最佳实践// 1. 锁范围最小化publicclassMinLock{privateintcounter=0;// 好:只锁必要部分publicvoidgoodIncrement(){// 前置逻辑(不需要锁)preLogic();synchronized(this){counter++;// 只锁核心操作}// 后置逻辑(不需要锁)postLogic();}// 不好:整个方法加锁publicsynchronizedvoidbadIncrement(){preLogic();// 不需要锁的操作也被锁住了counter++;postLogic();}}// 2. 避免锁对象变化publicclassLockObjectProblem{privateStringlock="lock";// 不好:字符串常量池共享publicvoiddoSomething(){synchronized(lock){// 可能有其他代码也用同样的字符串// ...}}}// 3. 使用专门的锁对象publicclassLockObjectGood{privatefinalObjectlock=newObject();// 好:私有专用对象publicvoiddoSomething(){synchronized(lock){// ...}}}// 4. 读写分离publicclassReadWriteLock{privatefinalReadWriteLockrwLock=newReentrantReadWriteLock();privatevolatileintvalue;publicintread(){rwLock.readLock().lock();try{returnvalue;}finally{rwLock.readLock().unlock();}}publicvoidwrite(intnewValue){rwLock.writeLock().lock();try{value=newValue;}finally{rwLock.writeLock().unlock();}}}

三、volatile vs synchronized

┌──────────────────────────────────────────────────────────────────┐ │ volatile vs synchronized 对比 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 特性 │ volatile │ synchronized │ │ ─────────────────┼────────────────────┼───────────────────── │ │ 原子性 │ 无 │ 有 │ │ 可见性 │ 有 │ 有 │ │ 有序性 │ 有(部分) │ 有(完全) │ │ 性能 │ 轻量(读屏障) │ 较重(系统调用) │ │ 使用场景 │ 状态标志/单例 │ 复杂同步逻辑 │ │ 底层实现 │ 内存屏障 │ Monitor Enter/Exit │ │ │ └──────────────────────────────────────────────────────────────────┘

四、总结

volatile 通过内存屏障保证可见性和有序性,适合状态标志等简单场景;synchronized 通过监视器锁保证三大事物,但有偏向锁→轻量级锁→重量级锁的升级机制。理解两者的底层原理,才能在并发编程中做出正确的选择。


系列导航

  • 上一篇:【JVM深度解析】第24篇:JVM内存模型(JMM)核心原理
  • 下一篇:【JVM深度解析】第26篇:CAS、AQS与并发工具类原理
  • 系列目录:JVM深度解析

参考资料

  1. JVM Lock Synchronization - Oracle
  2. Biased Locking in HotSpot
  3. Java Volatile Keyword
http://www.jsqmd.com/news/661280/

相关文章:

  • 3分钟解密:如何用Sharp-dumpkey找回丢失的微信聊天记录?
  • 如何用Go-CQHTTP构建你的专属QQ机器人:从零到一的完整指南
  • 云服务中断频发,企业如何平衡公共云可靠性与成本控制?
  • GHelper完整指南:3步告别华硕笔记本臃肿控制软件,体验轻量级极致性能管理
  • 真正让Claude Code效率翻倍的几个玩法
  • 自动化测试用例设计
  • 你的USB2.0设备总掉线?可能是这3个电路设计细节没做好(附EMC整改实测案例)
  • Flutter/React Native跨平台App如何做代码加固?2026年方案盘点
  • KS-Downloader:专业级快手无水印视频下载解决方案
  • Kubernetes StatefulSet 数据持久化实践
  • TCP三次握手流程
  • 雀魂AI助手:你的实时麻将策略分析教练免费使用指南
  • GEMMA混合模型基因组关联分析:技术原理深度解析与高效应用实战
  • Fortify扫描中Access Control: Database问题的3种实战绕过技巧(附代码)
  • 如何在Linux系统快速安装Photoshop CC 2022:完整解决方案指南
  • 终极远程管理神器:electerm如何彻底改变你的工作流?
  • Qwen2-VL-2B-Instruct快速上手:基于Dify打造无需编码的视觉AI应用
  • 保姆级教程:用MATLAB Simulink从零搭建汽车ABS防抱死系统模型(附PID调参技巧)
  • 软考中级-系统集成项目管理工程师-计算题专题
  • PHP基础知识——PHP环境安装
  • 9.【UPF】UPF Retention Strategies(UPF留存策略)
  • CBAM注意力机制实战:从原理到代码的即插即用指南
  • HarmonyOS6 ArkTS CheckboxGroup
  • Rust的闭包最佳实践
  • 终极指南:5分钟学会用FanControl掌控Windows风扇智能控制
  • 打破平台壁垒:在Windows上轻松安装安卓应用的三大突破
  • AI 搜索排名优化GEO系统 支持私有化源码部署与 OEM 贴牌,具备私有化部署能力与深度定制技术正在占据产业链的高价值环节 - 速递信息
  • React原理深入
  • 配置Anaconda Jupyter Notebook AI通用工作环境
  • QSpectrumAnalyzer终极指南:10分钟掌握专业SDR频谱分析工具