【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理

简介: JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。


image.gif 编辑

Hello大家好!👋 我是摘星✨,今天给大家带来的是《深入理解JMM:Java内存模型的核心原理与高并发实战》的学习!🚀

在多线程编程中,你是否遇到过变量值莫名“消失”线程间数据不同步,甚至单例模式失效的诡异问题?💡 其实,这些问题的根源往往在于对 JMM(Java Memory Model,Java内存模型) 的理解不够深入!

在本篇内容中,我们将:

拆解JMM的核心概念——主内存 vs 工作内存,揭秘线程间数据交互的底层逻辑;

深度剖析JMM三大特性(原子性、可见性、有序性),并对比 volatilesynchronized 的适用场景;

通过经典单例模式,分析 volatile 如何用内存屏障解决指令重排序问题;

从JDK底层 解读 volatile写屏障读屏障机制,彻底搞懂它的可见性原理!

无论你是面试突击 🎯 还是高并发实战优化 ⚡,这篇文章都能让你对JMM的理解提升一个Level!📈 快跟着我一起探索吧! 🔍💻

目录

5. JMM

5.1. JMM内存定义

5.2. JMM特性

5.3. 可见性

5.4. 有序性

5.4.1. 指令重排

5.4.2. 禁止重排

5.4.3. 指令重排示例

5.5. volatile


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指令并行优化的影响

特性

作用

实现方式

原子性

确保操作不可分割(如i++非原子,AtomicInteger原子)

synchronizedLockCAS(如AtomicInteger

可见性

线程修改后其他线程立即可见(解决CPU缓存不一致

volatilesynchronizedfinal(初始化后不可变)

有序性

防止指令重排序(如单例模式的双重检查锁volatile

volatile(内存屏障)、synchronized(代码块内有序)

5.3. 可见性

适用于只有一个线程修改变量值,有多个线程读取值的情况

volatile:用于修饰成员变量和静态变量,可以避免线程从自己的工作内存中查找变量的值,必须到主内存中获取变量的值,volatile操作的变量直接写到主内存中,这样就保证了线程之间的可见性,但是不能解决原子性

synchronized既可以解决线程之间的可见性问题,也可以解决原子性问题.但是synchronized操作更重量级,性能相对低

对比维度

volatile

synchronized

可见性

✅ 强制读写主内存

✅ 通过锁机制保证

原子性

❌ 不保证复合操作(如count+

✅ 保证代码块/方法内原子性

有序性

✅ 禁止指令重排序(内存屏障)

✅ 同步块内有序(as-if-serial语义)

性能

⚡ 轻量级(仅内存可见性)

⚠️ 重量级(线程阻塞/唤醒开销)

适用场景

状态标志(如boolean flag)、单例模式

多步骤复合操作(如转账

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;
    }
}

image.gif

5.5. volatile

volatile的底层实现原理是内存屏障Memory Barrier

volatile修饰的变量,会在其写指令之后加入写屏障,会在其读指令之前加入读屏障

写屏障:

  1. 保证本线程内的写指令前的指令不会重排序到其后,但是并不能保证读操作排到写屏障之前
  2. 保证写指令执行完毕后将变量值同步到主内存

读屏障:

  1. 保证读指令之后的共享变量全部从主内存中读取
  2. 保证读指令之后的指令不会排在读指令之前

volatile在JDK1.5之后才生效

屏障类型

作用

写屏障

1. 阻止屏障前的写操作重排到屏障后

2. 强制刷出工作内存到主内存(写操作后)

读屏障

1. 阻止屏障后的读操作重排到屏障前

2. 强制从主内存读取最新值(读操作前)

🎉 总结与展望

经过这篇的讲解,相信你已经对 JMM(Java内存模型) 有了更深入的理解!我们不仅剖析了 主内存与工作内存 的交互机制,还深入探讨了 原子性、可见性、有序性 这三大核心特性,并通过 volatilesynchronized 的对比,掌握了不同并发场景下的最佳实践。

🔹 关键回顾

  • **volatile** 适用于轻量级可见性控制(如状态标志、单例模式),但不保证原子性。
  • **synchronized** 能同时保证可见性+原子性,但性能开销较大,适合复杂同步场景(如转账操作)。
  • 指令重排序 虽然能优化性能,但在多线程环境下可能导致线程安全问题,而 volatile内存屏障机制可以有效禁止重排。

🔹 未来学习方向

如果你想进一步深入并发编程,可以研究:

CAS(Compare-And-Swap)Atomic 原子类

✅ **ThreadLocal 的内存泄漏问题**

✅ **ReentrantLocksynchronized 的性能对比**

🚀 实践出真知!建议你动手写几个多线程Demo,亲自体验 volatilesynchronized 的区别,这样才能真正掌握JMM的精髓!

💬 欢迎在评论区交流你的学习心得或遇到的并发问题,我们一起进步!下次见!👋

目录
相关文章
|
20天前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
59 1
|
5月前
|
Java 程序员 应用服务中间件
【高薪程序员必看】万字长文拆解Java并发编程!(2 2-2)
📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!💡 独家亮点抢先看:✅ 图解JVM内存模型(JMM)三大特性,看完再也不怕指令重排序✅ 手撕ReentrantLock源码,AQS队列同步器实现原理大揭秘✅ 全网最细线程状态转换图(附6种状态转换触发条件表)
104 0
|
5月前
|
网络协议 Java 大数据
【高薪程序员必看】万字长文拆解Java并发编程!(1)
📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!💡 独家亮点抢先看:✅ 图解JVM内存模型(JMM)三大特性,看完再也不怕指令重排序✅ 手撕ReentrantLock源码,AQS队列同步器实现原理大揭秘✅ 全网最细线程状态转换图(附6种状态转换触发条件表)
117 0
|
5月前
|
安全 Java 程序员
【高薪程序员必看】万字长文拆解Java并发编程!(2 2-1)
🔥【高薪程序员必看】万字长文拆解Java并发编程!面试官看了直呼内行,90%人不知道的线程安全骚操作!💻🚀《16个高频面试灵魂拷问+底层源码暴击》🔥👉戳这里看如何用1个月经验吊打3年程序员!📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!
107 0
|
5月前
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
109 0
|
5月前
|
Java 程序员
【高薪程序员必看】万字长文拆解Java并发编程!(3-2):并发共享问题的解决与分析
wait方法和notify方法都是Object类的方法:让当前获取锁的线程进入waiting状态,并进入waitlist队列:让当前获取锁的线程进入waiting状态,并进入waitlist队列,等待n秒后自动唤醒:在waitlist队列中挑一个线程唤醒:唤醒所有在waitlist队列中的线程它们都是之间协作的手段,只有拥有对象锁的线程才能调用这些方法,否则会出现IllegalMonitorStateException异常park方法和unpark方法是LockSupport类中的方法。
104 0
|
5月前
|
存储 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(4-1):悲观锁底层原理与性能优化实战
目录4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
88 0
|
3月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
1023 0
|
3月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
307 0