小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
JMM除了堆栈方法区的划分方式外,在线程与变量的角度上可以将其划分主内存 Main Memory和工作内存 Work Memory,其分别是线程共享与线程独占的。可以理解为主内存是 Heap 中对象实例的数据部分,工作内存是 JVM Stack 中的部分区域。
内存间的相互交互
内存模型
程序运行时面对的是Work Memory,因此MM与WM之间必定要制定变量传递方式,这些变量传递必须是原子性的,不可再分。
-
lock锁定:作用MM,标识某变量线程独占
-
unlock解锁:作用MM,标识lock的变量释放
-
read读取:作用MM,将MM的值传递到WM中
-
load载入:作用WM,将WM的值放入变量副本中
-
use使用:作用WM,将值传递给执行引擎
-
assign赋值:作用WM,将执行引擎的值赋值给WM的变量
-
store存储:作用WM,将WM的变量传送到MM中
-
write写入:作用MM,将变量值写入
要注意这只是 JVM 制定的一个规则,是脱离平台的抽象标准,实际情况需要在实现时结合平台进行具体设计。JVM使用这8种操作来进行内存交互操作,制定如下规则
-
read-load,store-write 必须成对出现
-
assgin 操作后需要将其 store 回主内存
-
不允许无故 store
-
变量只能在 MM 中产生,WM 的变量必须 read 来
-
lock 操作会清空 MM 中的 cache,引擎使用需要重新 load 和 use
-
unlock 前需要完成 store-write
要注意的是这些规则指定的只是顺序,而不是强制要求连续执行,这样在多线程的情况下多操作的原子不能保证。
volatile修饰符
volatile语义有两种:可见性、防止指令重排。主要由于在写操作后增加lock前缀指令,处理器收到该指令后执行两件事
-
当前处理器缓存行数据写会系统内存。
-
写回操作使其他CPU内的缓存该地址的数据无效。
这相当于要求对cache中的变量做了一次 store-write 操作。 这保证了 volatile 变量的可见性:当一条线程修改了这个变量的值,新值对于其他线程来说立即得知。在JMM的层面要求 use-load 和 assgin-stroe 操作必须成对且连续,这保证了每次都从缓存行中获取可能的新值。
但这个操作并不是原子的:这就造成了可能线程 更新变量 后 store 操作执行完后被 打断, 进行了 read 操作,这样读取到的是旧数据,因此 volatile 并不具备原子性。
另外 volatile 添加的 lock 是一个内存屏障,这就限制了内存重排的能力。
long与double型变量
32位的机器没有一次性写入64位数据的能力,因此double和long型数据在32位机器上不是原子性的。JVM对其有着宽松的 Nonatomic Treatment of double and long Variavles 协定。但这个问题随着64位机的来临不再存在了。