JMM:Java内存模型
Java内存模型(JMM)
参考:https://javaguide.cn/java/concurrent/jmm.html、https://www.javabetter.cn/thread/jmm.html
JMM 是什么?
JMM(Java Memory Model)出现的原因有两点(如下)。所以,才需要JMM 规定内存可见性规则和禁止特定的指令重排序,保证不同线程能看到正确的共享数据状态,实现多线程之间的正确交互。
- 因为在不同的操作系统上,内存模型不同。如果直接使用操作系统的内存模型,就违背了Java的跨平台特性。所以,Java语言才会制定JMM。
- JMM制定一套规范,确保在多线程环境下数据的一致性、支持高效的同步机制。
这里主要讲一下为什么会有多线程安全问题:指令重排。
指令重排
为了提升执行速度,计算机在执行程序代码的时候,会对指令进行重排序(即不一定按照写的代码顺序执行)。常见的 指令重排有两种:
- 编译器优化重排:编译器(包括 JVM、JIT 编译器等)在不改变单线程程序语义的前提下,重新安排语句的执行顺序。
- 指令并行重排:现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致,所以在多线程下,指令重排序可能会导致一些问题。解决办法:
-
对于编辑器,通过禁止编译器重排序,可禁止重排序。
-
对于处理器,通过插入内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)的方式来禁止特定类型的处理器重排序。
内存屏障是一种 CPU 指令,用来禁止处理器指令发生重排序(像屏障一样),从而保障指令执行的有序性。
JMM 工作原理
Java 内存模型(JMM) 抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。
首先,JMM 中的内存模型中,分为主内存、工作内存:
-
主内存:Java内存模型规定了所有的共享变量都存储在主内存中。
-
工作内存(本地内存):每个线程都有一个私有的工作内存,线程的工作内存中保存了该线程中使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
多线程下,对主内存的共享变量进行操作会诱发线程安全问题。而 JMM 定义了一系列的操作来保证线程的同步问题,他规定了如何做数据同步以及什么时候做数据同步。
JVM 内存结构 和 JMM 的区别
- JMM 主要关注的是多线程编程中的并发控制问题,通过一系列规范来保证数据的一致性和可见性。例如,Happens-before规则用于确保某些操作的顺序性。
- JVM内存结构 则更多地涉及具体的内存管理策略,如内存区域的划分和垃圾回收机制等,这些都直接影响到程序的性能和运行效率。
JMM 与 happens-before 原则?
虽然JMM提供了一个强大的内存模型,保证多线程之间的正确交互。但是另外一方面 编译器、处理器 又希望 JMM 的束缚少一点,这样就可以尽可能的做更多优化。JMM 提供了happens-before 规则,来达到一定的平衡。
happens-before 关系的定义:如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。