编辑
Hello大家好!👋 我是摘星✨,今天给大家带来的是《深入理解JMM:Java内存模型的核心原理与高并发实战》的学习!🚀
在多线程编程中,你是否遇到过变量值莫名“消失”、线程间数据不同步,甚至单例模式失效的诡异问题?💡 其实,这些问题的根源往往在于对 JMM(Java Memory Model,Java内存模型) 的理解不够深入!
在本篇内容中,我们将:
✅ 拆解JMM的核心概念——主内存 vs 工作内存,揭秘线程间数据交互的底层逻辑;
✅ 深度剖析JMM三大特性(原子性、可见性、有序性),并对比 volatile
和 synchronized
的适用场景;
✅ 通过经典单例模式,分析 volatile
如何用内存屏障解决指令重排序问题;
✅ 从JDK底层 解读 volatile
的写屏障与读屏障机制,彻底搞懂它的可见性原理!
无论你是面试突击 🎯 还是高并发实战优化 ⚡,这篇文章都能让你对JMM的理解提升一个Level!📈 快跟着我一起探索吧! 🔍💻
目录
5. JMM
5.1. JMM内存定义
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行
- 主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中
- 工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm
概念 |
说明 |
主内存 |
所有线程共享的内存区域,存储原始变量( |
工作内存 |
每个线程私有的内存副本,存储线程操作所需的变量副本( |
5.2. JMM特性
JMM的三大特性:
- 原子性:确保操作的是不可分割的,一个线程执行一个原子操作时,其他线程无法同时执行对同一变量的操作,保证了指令不会受到线程上下文切换的影响
- 可见性:当一个线程修改了共享变量的值后,其他线程能够立即看见修改后的变量值,保证指令不会受到CPU缓存的影响
- 对于用
volatile
关键字修饰的变量,JMM保证了读操作和写操作的可见性 - 对于没用
volatile
关键字修饰的变量,需要用到同步机制synchronized
来保证变量的可见性
- 有序性:程序的执行顺序必须符合开发者的预期,保证指令不会受到CPU指令并行优化的影响
特性 |
作用 |
实现方式 |
原子性 |
确保操作不可分割(如 |
|
可见性 |
线程修改后其他线程立即可见(解决 |
|
有序性 |
防止指令重排序(如 |
|
5.3. 可见性
适用于只有一个线程修改变量值,有多个线程读取值的情况
volatile
:用于修饰成员变量和静态变量,可以避免线程从自己的工作内存中查找变量的值,必须到主内存中获取变量的值,volatile操作的变量直接写到主内存中,这样就保证了线程之间的可见性,但是不能解决原子性
synchronized
既可以解决线程之间的可见性问题,也可以解决原子性问题.但是synchronized操作更重量级,性能相对低
对比维度 |
volatile |
synchronized |
可见性 |
✅ 强制读写主内存 |
✅ 通过锁机制保证 |
原子性 |
❌ 不保证复合操作(如 |
✅ 保证代码块/方法内原子性 |
有序性 |
✅ 禁止指令重排序(内存屏障) |
✅ 同步块内有序( |
性能 |
⚡ 轻量级(仅内存可见性) |
⚠️ 重量级(线程阻塞/唤醒开销) |
适用场景 |
状态标志(如 |
多步骤复合操作(如 |
5.4. 有序性
5.4.1. 指令重排
指令重排:在不影响最终结果的前提下,对指令的执行顺序进行重排序和组合,达到指令并行的效果.
指令重排不能缩短单条指令的运行时间,但是可以变相的提高整个程序的吞吐率
5.4.2. 禁止重排
对变量加上volatile关键字可以保证该变量之前的变量不会被重排到自己的后面
5.4.3. 指令重排示例
// 无volatile时可能发生重排序,导致其他线程看到instance未初始化完成 class Singleton { private static volatile Singleton instance; // 需volatile禁止重排序 private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 非原子操作(分配内存→初始化→赋值) } } } return instance; } }
5.5. volatile
volatile
的底层实现原理是内存屏障Memory Barrier
volatile
修饰的变量,会在其写指令之后加入写屏障,会在其读指令之前加入读屏障
写屏障
:
- 保证本线程内的写指令前的指令不会重排序到其后,但是并不能保证读操作排到写屏障之前
- 保证写指令执行完毕后将变量值同步到主内存
读屏障
:
- 保证读指令之后的共享变量全部从主内存中读取
- 保证读指令之后的指令不会排在读指令之前
volatile在JDK1.5之后才生效
屏障类型 |
作用 |
写屏障 |
1. 阻止屏障前的写操作重排到屏障后 2. 强制刷出工作内存到主内存(写操作后) |
读屏障 |
1. 阻止屏障后的读操作重排到屏障前 2. 强制从主内存读取最新值(读操作前) |
🎉 总结与展望
经过这篇的讲解,相信你已经对 JMM(Java内存模型) 有了更深入的理解!我们不仅剖析了 主内存与工作内存 的交互机制,还深入探讨了 原子性、可见性、有序性 这三大核心特性,并通过 volatile
和 synchronized
的对比,掌握了不同并发场景下的最佳实践。
🔹 关键回顾:
- **
volatile
** 适用于轻量级可见性控制(如状态标志、单例模式),但不保证原子性。 - **
synchronized
** 能同时保证可见性+原子性,但性能开销较大,适合复杂同步场景(如转账操作)。 - 指令重排序 虽然能优化性能,但在多线程环境下可能导致线程安全问题,而
volatile
的内存屏障机制可以有效禁止重排。
🔹 未来学习方向:
如果你想进一步深入并发编程,可以研究:
✅ CAS(Compare-And-Swap) 与 Atomic
原子类
✅ **ThreadLocal
的内存泄漏问题**
✅ **ReentrantLock
与 synchronized
的性能对比**
🚀 实践出真知!建议你动手写几个多线程Demo,亲自体验 volatile
和 synchronized
的区别,这样才能真正掌握JMM的精髓!
💬 欢迎在评论区交流你的学习心得或遇到的并发问题,我们一起进步!下次见!👋