简单了解JMM

什么是JMM

对于不同的硬件和操作系统,有着自己的底层内存模型,可能导致Java程序在一些的平台可以正确并发,而在另一些平台出现并发错误,JMM是Java内存模型,是语言级别的内存模型,用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM主要考虑的就是各种变量的访问规则,正确实现线程间的通信以及线程间的同步,对于JMM,把各种硬件抽象为本地内存和主存来存放变量。

总的来说JMM把硬件和操作系统的差异屏蔽了,让Java在不同机器上都可以正确的实现并发。

image-20240318203150822

内存间交互操作

关于变量如何从主存拷贝到本地线程以及变量如何从本地线程同步到主存的细节,JMM定义了8中操作来完成,对于每个线程来说,只能操作本地内存的变量,规定read、load要顺序执行,store、write要顺序执行。

image-20240318205713894

操作系统的三大问题

在谈论JMM之前,首先我们需要了解操作系统的几个问题,JMM正是解决了操作系统这些问题。

随着时代的发展,计算机逐步更新换代,人们对于计算机的要求越来越高,对于家庭或者公司的电脑需要人机交互,这就要求计算机需要有较短的响应时间,为此工程师引入了CPU的多级缓存、时间片轮转调度算法、指令优化重排,但是也相应的带来了问题:

  • CPU缓存一致性问题(可见性),解决方案:总线锁、MESI协议

  • 代码被多个线程访问的并发问题(原子性),解决方案:锁

  • 指令没有按照程序员的意愿执行(有序性)

JMM抽象的问题

可见性:线程本地变量和内存的可见性问题,规定线程只能操作本地内存变量,线程对内存的修改无法及时被其它线程看到

原子性:如 i++ 语句,底层由多条指令组成,CPU实际执行的是指令,在指令执行期间可能会发生时钟中断切换线程,最终导致程序执行结果出问题

有序性:如 Java 创建对象过程,类加载 -> 实例化 -> 初始化 -> 引用赋值,由于指令重排可能导致引用赋值在初始化之前,导致其它线程使用到未初始化的对象

Java关键字如何解决问题

  • 可见性:

    • 可通过volatile关键字解决,强制线程每次修改本地变量需要同步到主存,同时每次读取本地变量都需要从主存读取

    • 可通过synchrinized关键字解决:锁获取和volatile读有相同的语义,将本地变量置为无效状态,锁释放和volatile写有相同的语义,变量需要同步到主存

    • 可通过final关键字解决

  • 原子性:可通过synchrinized关键字解决,底层是通过monitorenter和monitorexit字节码指令

  • 有序性:

    • 可通过volatile关键字解决,volatile规则

    • 可通过synchrinized关键字解决,程序次序规则(只有一个线程访问同步代码块,单线程重排优化语义是一样的)

Happens-Before原则

如果JMM中所有的有序性仅靠volatile和synchronized来完成,未免很多操作有点啰嗦,我们在日常编码的过程中并不会过多关注有序性,因为有个Happens-Before原则来辅助保证代码操作的顺序性

常见的Happens-Before原则:

  • 程序次序规则:同一个线程内,对于顺序执行,前面的操作先行于后面的操作

  • 管程锁定规则:对于一个锁,unlock先行于lock

  • volatile规则:对于一个用volatile修饰的变量,写操作先行于读操作

  • 线程启动规则:start()先行于这个线程的其它动作

  • 线程终止规则:线程所有动作先行于终止检查

  • 线程中断规则:interrupt()先行于检测到中断事件的发生

  • 对象终止规则:对象的初始化完成先行于销毁

  • 传递性:A操作先行于B操作,B操作先行于C操作,那么A操作先行于C操作

如下面这个程序,你不能保证 i = 1 一定会在 j = 2 前执行,但是这也不影响Happens-Before原则的正确性,因为这并不影响整个程序执行结果的正确性。

 int i = 1;
 int j = 2;

<think>嗯,用户问的是JMM内存屏障解决了什么问题。我需要先回忆一下Java内存模型(JMM)的基础知识。内存屏障,听起来和指令重排、可见性有关吧。 首先,JMM是为了解决多线程下的内存可见性和有序性问题。那内存屏障在这里起什么作用呢?可能和防止编译器和处理器进行指令重排序有关。比如,单例模式里的双重检查锁,如果不加volatile,可能会因为指令重排导致问题,这时候内存屏障应该就派上用场了。 然后,用户可能需要知道内存屏障具体解决了哪些问题。应该是三个主要问题:可见性、有序性,还有防止重排序。比如,一个线程修改了变量,另一个线程可能看不到,这时候写屏障和读屏障能强制刷新内存,保证可见性。而指令重排会导致代码执行顺序和预期不一致,内存屏障可以禁止这种重排。 接下来要分点解释。每个问题对应内存屏障的作用。比如,可见性问题,通过Store和Load屏障来同步主存和本地内存。有序性问题,通过不同类型的屏障限制重排序,比如StoreStore屏障确保前面的写操作先于后面的。 还要注意例子,比如volatile变量的读写如何插入内存屏障。可能还要提到happens-before原则,内存屏障是实现这些规则的基础。 用户可能不太了解底层细节,所以需要用简单的中文解释,避免太技术化。同时要结构清晰,分点回答,帮助用户一步步理解。需要检查是否符合之前的系统指令,比如行内公式用$...$,独立公式用$$...$$,不过这个问题可能不需要数学公式,但如果有涉及变量的地方要注意格式。 可能还需要提到不同的内存屏障类型,LoadLoad、StoreStore等,以及它们的具体作用。最后总结内存屏障的重要性,在并发编程中的实际应用,比如volatile、synchronized、CAS操作背后的原理。 需要确保回答正确,比如JMMJava Memory Model,内存屏障是JMM用来控制内存可见性和顺序的一种机制。避免错误,比如不要把内存屏障和CPU层面的屏障混淆,但可以提到JMM的内存屏障是抽象概念,具体实现可能依赖处理器指令。</think>Java内存模型(JMM)中的内存屏障(Memory Barrier)主要解决了多线程环境下的三个核心问题: --- ### 一、可见性问题 **问题表现**:一个线程对共享变量的修改,其他线程可能无法立即感知(例如:线程A修改变量后,线程B仍读取旧值)。 **原因**:CPU缓存与主存的数据同步延迟,以及编译器/处理器的优化导致写操作未及时刷新到主存。 **内存屏障作用**: - **写屏障(Store Barrier)**:强制将本地缓存(如CPU寄存器、缓存)的修改刷新到主存。 - **读屏障(Load Barrier)**:强制从主存重新加载最新值到本地缓存。 **示例**: ```java // volatile变量通过内存屏障保证可见性 volatile int flag = 0; void threadA() { flag = 1; // 写操作后插入Store屏障 } void threadB() { while (flag == 0); // 读操作前插入Load屏障 } ``` --- ### 二、有序性问题 **问题表现**:代码的实际执行顺序可能与编写顺序不一致(指令重排序优化导致)。 **原因**:编译器/处理器为提高性能可能对指令进行重排序。 **内存屏障作用**: - **禁止重排序**:通过插入特定类型的内存屏障,限制编译器和处理器的重排序行为。 **示例**: ```java // 单例模式的双重检查锁 public class Singleton { private static volatile Singleton instance; // volatile禁止指令重排 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 无volatile时可能发生重排序(对象未初始化完成就被赋值) } } } return instance; } } ``` --- ### 三、内存操作原子性问题(间接解决) **问题表现**:复合操作(如long/double的非原子写)可能被其他线程观察到中间状态。 **内存屏障作用**: - 通过限制指令重排序和强制内存同步,间接保证操作的原子性表现。 --- ### 内存屏障的四种类型 JMM定义了以下屏障(实际由编译器/JVM具体实现): 1. **LoadLoad屏障** - 确保屏障前的读操作先于屏障后的读操作完成。 - 示例:`Load X; LoadLoad; Load Y` → 保证X的读取在Y之前完成。 2. **StoreStore屏障** - 确保屏障前的写操作先于屏障后的写操作刷新到主存。 - 示例:`Store X; StoreStore; Store Y` → 保证X的写入在Y之前可见。 3. **LoadStore屏障** - 确保屏障前的读操作先于屏障后的写操作完成。 4. **StoreLoad屏障** - 确保屏障前的所有写操作对其他线程可见后,才执行屏障后的读操作(开销最大)。 --- ### 总结 内存屏障通过以下方式支撑JMM的**happens-before原则**: 1. 强制同步主存与本地缓存,解决可见性问题。 2. 限制指令重排序,解决有序性问题。 3. 为volatile、synchronized、CAS等并发工具提供底层支持。 **实际应用场景**: - volatile变量的读写 - synchronized代码块入口/出口 - CAS操作(如AtomicInteger) - Thread.start()和Thread.join()的隐式屏障
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值