Java内存模型
Java内存模型
Java内存模型规范(JSR-133)是围绕原子性、可见性和有序性展开的。
Java内存模型(Java Memory Model,JMM)规定了Java程序中多线程并发访问变量时,变量值的可见性、原子性和有序性的保证规则。
JMM定义了一套Java虚拟机规范,规定了Java虚拟机对于内存的读写操作、指令重排序和多线程同步的行为。JMM的设计目标是使Java程序在不同的Java虚拟机和操作系统平台上都能够有相同的行为。
具体来说,JMM规定了以下几个方面:
- 所有的变量都存储在主内存中,每个线程都有自己的工作内存,线程的读写操作都必须通过主内存进行。
- 在每个线程的工作内存中,包含了该线程使用到的变量的副本拷贝。
- 所有的变量都是volatile、synchronized和final变量时,会保证线程读写变量的原子性和可见性。
- 对于非volatile和非final变量,线程对变量进行读写操作前,必须先从主内存读取变量值到工作内存,操作完成后再将工作内存的值写回主内存。
- 在Java内存模型中,指令重排序是允许的,但是JMM规定了一系列的happens-before规则来确保指令重排序不会导致程序的执行结果发生错误。
Java内存模型的设计目标是提供一种高效、灵活和安全的多线程编程模型。同时,在实际开发中,开发者需要了解Java内存模型的规范,合理地使用volatile和synchronized关键字以及其他同步机制,保证多线程程序的正确执行。
原子性、可见性、有序性
原子性、可见性、有序性
如下图说明了Java线程、工作内存和主存之前的关系。
Java多线程、工作内存和主内存关系
Java内存模型(JMM)定义了一套自己的主存到工作内存之间的交互协议,即一个变量如何从主存拷贝到工作内存,又如何从工作内存写入主存,该协议包含8种操作,并且要求JVM具体实现必须保证其中每一种操作都是原子的、不可再分的。 8种操作分别是:
JMM8大原子操作
如下图描述了以上8个操作所在位置:
什么是指令重排,为什么需要?
要搞懂指令重排,首先要知道一条指令在CPU内是如何执行的,如下图约5个步骤。
CPU指令执行
为了加快指令并行速度,CPU硬件支持了流水线技术。
CPU流水线
不同的指令步骤执行在不同的硬件局部,从而可以支持同时并发执行。
CPU流水线硬件布局
知道了CPU流水线之后,我们来看一个A=B+C的流水线执行过程例子:
指令执行过程
如果按串行排列,则耗时4 * 5 = 20个时钟周期;使用CPU流水线并行技术后,可以只消耗9个时钟周期,节省了11个时钟周期的时间。所以流水线技术的引入,大大提高了CPU并行执行速度。 再看如下图的例子:
流水线执行过程
多条语句执行时,通过指令重排可以消除一些CPU中断,从而缩短执行时间,加快执行速度。
重排序
执行令重排序
对于Java语言来说,为了提高新能,从源码到得到指令执行序列可能会经过编译器重排序和CPU重排序;CPU重排序又分为指令级重排和内存系统重排。
编译器重排序
编译器重排序指的是在代码编译阶段进行指令重排,不改变程序执行结果的情况下,为了提升效率,编译器对指令进行乱序(Out-of-Order)的编译。
CPU重排序
流水线(Pipeline)和乱序执行(Out-of-Order Execution)是现代CPU基本都具有的特性。
所谓“乱序”,仅仅是被称为“乱序”,实际上也遵循着一定规则:只要两个指令之间不存在“数据依赖”,就可以对这两个指令乱序。
CPU重排序包括两类:指令级重排序和内存系统重排序。
- 指令级重排序 在不影响程序执行结果的情况下,CPU内核采用ILP(Instruction-Level Parallelism,指令级并行运算)技术来将多条指令重叠执行,主要是为了提升效率。如果指令之间不存在数据依赖性,CPU就可以改变语句的对应机器指令的执行顺序,叫作指令级重排序。
- 内存系统重排序 对于现代的CPU来说,在CPU内核和主存之间都具备一个高速缓存,高速缓存的作用主要是减少CPU内核和主存的交互(CPU内核的处理速度要快得多),在CPU内核进行读操作时,如果缓存没有的话就从主存取,而对于写操作都是先写在缓存中,最后再一次性写入主存。
由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
哪些指令不能重排:Happen-Before原则(先行发生)
happen before8大原则