面试题:请介绍⼀下 JMM(Java 内存模型)

本文深入探讨了Java内存模型(JMM)及其与CPU缓存一致性协议的关系,解释了内存可见性问题的来源,强调了volatile关键字在保证变量可见性上的作用。此外,还介绍了内存重排序的现象和内存屏障的概念,以及它们如何通过防止编译器和CPU的指令重排序来确保数据一致性。最后,提到了Java中Unsafe类提供的内存屏障函数,用于进一步控制内存操作的顺序。

面试题:请介绍⼀下 JMM(Java 内存模型)


关键词

  • CPU缓存一致性协议(例如MESI),多个CPU核心之间缓存不会出现不同步的问题
  • Store Buffer、Load Buffer和L1之间却是异步的缓存主内存之间不是完全同步的)
  • 内存重排序(Memory Ordering):Store Buffer的延迟写入
  • CPU内存重排序 造成 内存可见性 问题
  • volatile保证变量的可见性:每次使⽤它都到主存中进⾏读取
  • 内存屏障:禁止编译器重排序和 CPU 重排序

一、为什么会存在“内存可见性”问题

下图为x86架构下CPU缓存的布局,即在一个CPU 4核下,L1、L2、L3三级缓存与主内存的布局。每个核上面有L1、L2缓存,L3缓存为所有核共用
在这里插入图片描述

因为存在CPU缓存一致性协议,例如MESI,多个CPU核心之间缓存不会出现不同步的问题,不会有“内存可见性”问题。

缓存一致性协议对性能有很大损耗,为了解决这个问题,又进行了各种优化。例如,在计算单元和L1之间加了Store Buffer、Load Buffer(还有其他各种Buffer),如下图:

在这里插入图片描述

L1、L2、L3和主内存之间是同步的,有缓存一致性协议的保证,但是Store Buffer、Load Buffer和L1之间却是异步的。向内存中写入一个变量,这个变量会保存在Store Buffer里面稍后才异步地写入L1中同时同步写入主内存中。

操作系统内核

### Java JMM内存模型相关面试题解释 #### 什么是Java内存模型JMM)? Java内存模型Java Memory Model, JMM)是一种规范,定义了多线程环境下变量的访问规则以及线程间通信的原则[^3]。它描述了主内存与工作内存之间的关系,并规定了线程如何读写共享变量以确保数据一致性。 #### JMM的主要目标是什么? JMM的目标是屏蔽底层硬件和操作系统的差异,为开发者提供统一的内存访问视图,从而保证Java程序在不同平台上具有一致的并发行为[^2]。 #### JMM的核心概念有哪些? 1. **主内存(Main Memory)** 所有线程共享的内存区域,存储了实例字段、静态字段和数组元素等变量。它是所有对象和变量的最终存储位置[^5]。 2. **工作内存(Working Memory/Thread Local Memory)** 每个线程都有自己独立的工作内存,其中保存了从主内存复制过来的变量副本。线程对变量的操作均在其工作内存中进行,随后再同步回主内存[^5]。 3. **原子性(Atomicity)** 原子性指的是某个操作不可被中断,要么全部执行成功,要么完全不执行。大多数基本数据类型的读写操作具有原子性,但对于`long`和`double`类型,在某些情况下可能存在非原子性问题。复杂操作通常需要借助`synchronized`或显式的锁机制来保证原子性。 4. **可见性(Visibility)** 当一个线程修改了共享变量的值时,其他线程能够及时感知这一变化。`volatile`关键字可以通过禁止指令重排序并强制每次访问都从主内存加载最新值的方式,确保变量的可见性。 5. **有序性(Ordering)** 即使代码按顺序书写,但由于编译器优化或处理器乱序执行等原因,实际运行过程中可能出现指令重排现象。JMM通过`happens-before`原则约束特定操作间的相对顺序,防止因重排引发错误。 #### 如何解决“内存可见性”问题? 内存可见性问题是由于缓存的存在而导致的一个线程无法立刻获取另一个线程所作更改的情况。以下是几种常见的解决方案: - 使用`volatile`修饰符:它可以阻止指令重排,同时确保每次使用该变量都会直接从主内存读取其当前值[^4]。 - 利用`synchronized`关键字:进入同步块之前会清空本地缓存并将修改后的数据刷新至主内存;退出时也会重新载入最新的全局状态[^5]。 - 显式调用内存屏障(Memory Barriers/Fences):这类低级工具能有效控制何时停止进一步处理直到先前所有的改动都被提交完毕为止。 #### 示例代码展示 下面给出一段利用`volatile`实现简单计数功能的例子: ```java public class VolatileExample { private static volatile int count = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; ++i) { increment(); } }); Thread t2 = new Thread(() -> { while (count < 1000) {} System.out.println("Count reached 1000"); }); t1.start(); t2.start(); t1.join(); // Wait until thread finishes execution. } private static synchronized void increment() { // Synchronization ensures atomicity here. count++; } } ``` 此例子展示了两个线程分别负责增加计数值与监控进度的任务流程。这里采用`synchronized`方法不仅实现了增量动作本身的独占权管理还间接促进了跨进程间消息传递效率提升因为锁释放前必然伴随一次完整的上下文切换过程包括但不限于清理临时寄存单元内容物并向外部暴露更新成果等等一系列连锁反应共同构成了整个事务链条不可或缺的重要环节之一[^5]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿城大饼

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值