Java内存模型(Java Memory Model,简称JMM),是Java在并发模型中内存的工作模型。Java的并发采用的是共享内存模型。
内存模型的抽象结构
Java中所有的实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享。局部变量、方法定义参数和异常处理参数不会在线程之间共享。
Java线程之间的通信由Java内存模型控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。JMM定义了线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了改线程对共享变量的操作副本。上述的本地内存和线程工作内存是一种抽象的概念,它涵盖了缓存、写缓冲区、寄存器以及其它的硬件和编译器优化。
同步与指令重排序
线程之间的同步用的是共享内存模型,比如线程A和B要通信。首先需要A将本地内存的变量值更新到主内存,然后线程B从主内存读取此变量的值。
在系统执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分成3种类型。
1,编译器重排序。
2,指令级并行重排序。
3,内存系统重排序。
重排序导致程序真正执行的顺序与编写的代码顺序不完全一致。
JMM会通过内存屏障来规定程序执行时必须遵守的顺序。
无论怎么重排序,系统会使得程序在单线程执行时结果不变。也就是在单线程存在依赖关系的指令不会重排序的。但是在多线程的情况下就可能存在问题。
重排序会导致多个线程读写共享变量时存在不确定性。而JMM为此提供最小安全性,也就是变量的值要么是其它线程写入之前默认值或写入之后的值,不会无中生有。
简单了解一下旧的内存模型
旧的内存模型(在JDK5之前),Java主要是依靠规定了8种对内存访问语句以及规则。比如如何执行变量的读/写,加锁/解锁,以及volatile变量的读/写等。旧Java内存模型通过这些复杂的规则,来保证多线程程序的线程之间,可以可靠地传递共享变量,从而保证多线程程序的正确性。
新的内存模型
旧的内存模型很复杂,JDK5之后,JMM采用新的内存模型。新的内存模型使用happens-before的概念来阐述操作之间的内存可见性。happens-before的定义是下面两点出发的:
1,对于编程人员来说:如果一个操作happens-before另一个操作,那么第一个操作的在第二个操作之前执行,且执行结果对第二个操作结果可见。
2,对于编译器来说:两个操作之间存在happens-before关系时不一定要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
从JMM角度看volatile
volatile是一个轻量级同步机制。JMM读取volatile变量时不会从本地内存读取,而是从主内存读取。这一点可以保证读取最新的值。
volatile的主要作用是:1,保证可见性;2,禁止指令重排序
对于第一点,JVM会禁止使用线程本地变量,每次都使用主内存变量。
对于第二点,JVM为其变量添加内存屏障,系统处理此屏障时就不会重排序。
从JMM角度看锁
锁是Java中非常重要的同步机制。
获取锁时,JMM会把该线程对应的本地变量设置为无效,使得临界区必须从主内存读取共享变量。
释放锁时,JMM会把该线程对应的本地变量刷新到主内存中。