JMM(Java Memory Model)是什么?:
JMM是一份规范,它规定了 JVM 在多线程并发访问共享内存中的数据时,线程的行为规范和模型。
JMM 规定了所有的变量都存储在主内存中,每个线程都有自己的工作内存,线程对变量进行操作时需要先把变量从主内存中复制到工作内存中,操作完成后再将结果写回主内存。
解决了什么问题?
不同的操作系统都有自己的一套内存模型,如果直接复用操作系统层面的内存模型,就可能会导致同样一套代码换了一个操作系统就无法执行了。Java 语言是跨平台的,它需要自己提供一套内存模型以屏蔽系统差异,将内存模型的复杂操作内聚起来。对于 Java 开发者说,你不需要了解底层原理,直接使用并发相关的一些关键字和类(比如 volatile、synchronized、各种 Lock)即可开发出并发安全的程序
volatile特性:
-
保证了内存可见性。
对于使用volatile修饰的变量,当线程改变了他的值,JMM会立即将其刷新到主内存中;每次使用前,再从主内存中读出 -
防止指令重排序
JMM会使用指令实现内存屏障功能,禁止读写该变量前后语句的重排序优化,保证编译后代码的执行顺序和程序中编写的一致 -
不具备原子性
对于复合操作不具备原子性。所以在对 volatile 修饰变量的复合操作中,应使用synchronized或Lock来同步,例如 volatileInt++ 是一个复合操作,在多线程环境下,就需要加锁同步。
专题分析
指令重排序
什么是指令重排序?
简单来说就是系统在执行代码的时候并不一定是按照你写的代码的顺序依次执行。
常见的指令重排序有下面 2 种情况:
- 编译器优化重排 :编译器(包括 JVM、JIT 编译器等)在不改变单线程程序语义的前提下,重新安排语句的执行顺序。
- 指令并行重排 :现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
Java 源代码会经历 编译器优化重排 —> 指令并行重排 —> 内存系统重排 的过程,最终才变成操作系统可执行的指令序列。
编译器和处理器的指令重排序的处理方式不一样。对于编译器,通过禁止特定类型的编译器重排序的方式来禁止重排序。对于处理器,通过插入内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)的方式来禁止特定类型的处理器重排序。指令并行重排和内存系统重排都属于是处理器级别的指令重排序。
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种 CPU 指令,用来禁止处理器指令发生重排序(像屏障一样),从而保障指令执行的有序性。另外,为了达到屏障的效果,它也会使处理器写入、读取值之前,将主内存的值写入高速缓存,清空无效队列,从而保障变量的可见性。