JUC: Java内存模型JMM
1 常见面试题
什么是JMM?
JMM和volatile两个之间的关系是什么?
JMM有哪些特性?
为什么要有JMM?为什么出现?作用和功能是什么?
happens-before先行发生原则你有了解吗?
2 Java内存模型-JMM
CPU运行并不是直接操作物理内存而是先将内存中的数据读到CPU缓存,而CPU对内存的读写操作就可能造成数据不一致的问题。
JVM规范中定义了一种Java内存模型,来屏蔽掉各种硬件和操作系统的内存访问差异。用来实现Java程序在各个平台下都能达到一致的内存访问效果.
它通过定义一套标准的规范,为Java程序员提供了一个稳定、跨平台的并发编程基石。
| 硬件/软件层面的挑战 | 导致的问题 | JMM的解决方案 |
|---|---|---|
| CPU多级缓存 | 缓存一致性问题,导致线程对共享变量的修改彼此不可见。 | 定义主内存与工作内存的交互规则,通过关键字(如volatile)强制刷新内存。 |
| 处理器优化与指令重排 | 为优化性能,编译器和处理器会重排指令执行顺序,破坏程序的有序性,可能导致意想不到的结果。 | 建立 happens-before 规则,并在关键位置插入内存屏障,禁止有害的重排序。 |
| 平台差异性 | 不同硬件架构(如x86、ARM)的内存模型差异,导致同一段Java并发程序在不同平台行为不一致。 | 提供统一的内存访问模型抽象,屏蔽底层差异,实现 “一次编写,到处运行” 的并发语义。 |
JMM(ava内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
作用:屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各个平台上都能达到一致的内存访问效果。
3 JMM三大特性
- 可见性
- 原子性
- 有序性(禁止指令重排)
(1)可见性
当一个线程修改了某个共享变量,其他线程是否能够立即获取变更,JMM规定了所有的变量都存储在主内存中.
系统主内存共享变量的数据修改被写入的时机是不确定的,多线程并发下很可能出现脏读,所以每个线程都有自己的工作内存,线程自己的工作内存保存了该线程使用到的变量的主内存数据副本,线程对变量所有的操作都必须在自己的工作内存进行。线程间变量值的传递需要依靠主内存来完成。
(2)原子性
指一个操作是不可以打断的,即多线程环境下,操作不能被其他线程干扰。
(3)有序性
JVM在执行Java代码指令的时候,执行顺序可能和Java代码顺序不一致,这个指令顺序重新编排的过程叫指令重排序。
指令重排可以保证单线程的语义一致,但是不能保证多线程的语义也一致。
4 JMM规范下,多线程对变量的读写过程
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到的线程自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成.
5 JMM规范下,多线程先行发生原则happends-before
在JVM中,如果一个操作执行的结果需要对另一个操作可见性,或者代码重排序,那么这两个操作之间必须存在happens-before(先行发生)原则。逻辑上有先后关系。
包含可见性和有序性的约束。
我们没有时时、处处、次次,添加volatile和synchronized来完成程序,这是因为Java语言中JMM原则下有一个“先行发生”(Happens-Before)的原则限制和规矩,给你立好了规矩!
这个原则非常重要: 它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩难懂的底层编译原理之中。
(1)happens-before总原则
- 可见:如果一个操作
happens-before另一个操作,那么第一个操作对第二个操作可见 - 重排:如果两个操作之间存在
happens-before关系,并不一定按照happens-before的顺序进行执行,只要结果一致,执行顺序可能改变。
(2)happens-before 八大原则
- 次序规则:
一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作 - 锁定规则:获取锁一定是在锁空闲的时候,释放锁必须先拥有锁
- volatile变量规则:对于volatile变量的写操作,先行发生于后面的读操作。
- 传递规则:如果操作A先于操作B,操作B又先于操作C,那么操作A先于操作C
- 线程启动规则:一个线程的start()方法优先于线程内的每一个操作。
- 线程中断规则:先进行了
interrupt()后,才能被检查线程中断状态 - 线程终止规则:线程中的所有操作都先行于此线程的终止操作。
- 对象终结规则:创建过对象才能进行垃圾回收
finalize()

浙公网安备 33010602011771号