java 内存模型用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 java 程序在各种平台下都能达到一致的内存访问效果。主要目标是定义程序中各个变量的访问规则。
主内存与工作内存
Java 内存模型规定了所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如图所示:
内存间交互操作
java 内存模型定义了 8 种原子操作来完成内存交互操作(变量从主内存拷贝到工作内存,从工作内存同步到主内存的实现细节。
-
lock(锁定):作用于主内存的变量,一个变量在同一时间只能一个线程锁定,该操作表示这条线成独占这个变量
-
unlock(解锁):作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定
-
read(读取):作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用
-
load(载入):作用于线程的工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言的)
-
use(使用):作用于线程的工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作
-
assign(赋值):作用于线程的工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作
-
store(存储):作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用
-
write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中
volatile
volatile 变量的特性:
- 保证此变量对所有线程的可见性(一条线程修改了这个变量的值,新值对于其他线程来说是立即得知的)。
- 禁止指令重排序优化。
加了 volatile 后,编译成汇编代码,赋值后多执行一个“ lock add1 $0x0,(%esp)”操作,这个操作相当于一个内存屏障,从排序时不能把后面的指令重排序到内存屏障之前的位置。lock 前缀的作用是使得本 CPU 的 cache 写入内存,该写入动作也会引起别的 CPU 或者别的内核无效化其 Cache,使得 volatile 变量的修改对其他 CPU 立即可见。
先行发生原则
先行发生原则是Java内存模型中定义的两个操作之间的偏序关系。比如说操作A先行发生于操作B,那么在B操作发生之前,A操作产生的“影响”都会被操作B感知到。
Java内存模型自带先行发生原则
- 程序次序原则
在一个线程内部,按照代码的顺序,书写在前面的先行发生与后边的。或者更准确的说是在控制流顺序前面的先行发生与控制流后面的,而不是代码顺序,因为会有分支、跳转、循环等。 - 管程锁定规则
一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须注意的是对同一个锁,后面是指时间上的后面 - volatile变量规则
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的后面是指时间上的先后顺序 - 线程启动规则
Thread对象的start()方法先行发生与该线程的每个动作。 - 线程终止规则
线程中的所有操作都先行发生与对此线程的终止检测,可以通过Thread.join()和Thread.isAlive()的返回值等手段检测线程是否已经终止执行 - 线程中断规则
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。 - 对象终结规则
一个对象的初始化完成先行发生于他的finalize方法的执行。 - 传递性
如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。