java 内存模型的主要目的是定义程序中各个变量的访问规则。就是虚拟机中将变量存储到内存中,以及从内存中取出的底层细节。
这里的变量和 java 中所定义的变量不同,这里所说的变量包括实例字段,静态字段和构成数组对象的元素,不包括局部变量和方法参数化(应为这两个是线程私有的)。
主内存和工作内存
上面所说的变量都位于主内存当中。每个线程都有自己的工作内存。工作内存中保存了这个线程对所用到的变量在主内存的拷贝对象,线程对变量的操作都是在工作内存当中进行的,也就是说都是对拷贝副本的操作,不能直接操作主内存中的变量。不同的线程也不能操作其他线程中的变量,交互必须通过主内存。
要清楚这里的主内存和工作内存的内层划分和堆栈方法区没有关系,这两者是不同层次的内存划分。

主内存和工作内存的交互操作
java 虚拟机规定了 8 中操作
lock:作用于主内存的变量,表明该变量现在被一条线程独自占有。
unlock:作用于主内存的变量,表明被线程独自占有的变量被释放。
read:作用于主内存的变量,把变量的值传输到工作内存中,以便下一步 load 操作。
load:作用于工作内存当中,把 read 操作得到值存入工作内存的副本当中。
use:作用于工作内存的变量,将 load 得到的值传给执行引擎。每当虚拟机遇到一个·需要使用到变量的值的字节码指令·的时候都会进行这个操作。(句读之不知)
assign:作用于工作内存的变量,将从执行引擎获得的值赋值给工作内存中的变量。
store:将工作内存当中的值传送到主内存。以便 write 操作。
write:将 store 得到的值放到主内存的变量中。
8 种交互操作执行的规则
不允许 read 和 load、store 和 write 操作之一单独出现。意思是不允许从主内存读取变量的值然后工作内存不接受,反之也是。
在工作内存中进行 assign 操作必须要同步回主内存。
工作内存不能够在没有进行任何 assign 操作的时候进行同步主内存的操作
工作内存当中不能够使用没有被赋值的变量。新的变量只能在主内存当中生成,惠安句话就是说 use 之前必须 load。store 之前必须 assign(细细品味这个,和上一点也有关系)
一个变量只能够被一个线程进行 lock 操作,并且可以进行多次 lock 操作,但是必须进行相同次数的 unlock 操作才能解锁变量。
如果对一个变量执行 lock 操作,会清空工作内存中这个变量的值,在执行引擎使用这个变量的之前必须执行 load 或者 assign 操作。
如果一个对象没有被 lock 就不能执行 unlock 操作。
在执行 unlock 之前,要把工作内存中的变量传到主内存当中。
特殊的规则 volatile
这其实是我看 java 内存模型的可以说是导火索把:)
volatile 是虚拟机提供的最轻量的同步机制。
被 volatile 声明的变量对所有线程具有可见性,这就意味着当你在一个线程的工作内存中修改了这个变量,那么其他线程会立刻知晓,所以这个变量在各个线程中是一致的。
这里有一个误区,那就是被 volatile 声明的变量并不是并发安全的。原因是因为 java 的运算操作并不是原子操作。
不如说 a++;这个运算,他的字节码指令是这样的:
getstatic
iconst_1
iadd
putstatic
已经是 4 条指令来完成运算,所以说这个运算不是原子操作。
其实这里也不是很严谨,因为即使是一条字节码指令,也有可能解释器需要多行代码才能解释指令,jit 需要多条本地指令完成这个字节码指令。
所以说仍然需要加锁来保证原子性操作。在下面两种情况下,不需要加锁
运算结果不依赖于当前变量的值,或者说能够确保只有单一线程可以修改变量的值。
变量不需要和其他的变量一同参与不变约束。
除了可见性,volatile 还有余个语义就是禁止指令重排序优化,这里的规则是在复制操作后多了一个 lock 指令,在 cpu 执行指令的时候,在 lock 指令后的指令无法咋 lock 指令之前执行,也就是所谓的内存屏障。
这里在提及一下 cpu 乱序执行(指令重排序)的概念。
指令重排序是指 cpu 采用了允许将多条指令不按程序规定的顺序分开发送给相应的电路单元处理。这里的乱序不是随意的,cpu 要能正确处理指令依赖的情况来保证程序得到正确的结果。
volatile 是轻量级的同步机制,说他轻量级,那她在性能方面肯定有一定的优势,尤其是在变量读取这方面,是跟普通变量的读取性能消耗差不多的,但是在写方面,可能会慢一些。
long,double 的特殊规则
之前说的内存交互的 8 种操作都是原子性的,但是,如果操作对象是 long 或者是 double,并且没有用 volatile 修饰,那么虚拟机允许这 8 种操作分成两次 32 位的操作。但是虚拟机虽然允许,但是不建议这样做,还是希望实现虚拟机时将这些操作也实现成原子性操作。



