一、内存可见性
1.1、CPU缓存一致性
简单来讲,就是当某一核改变了共享变量的值,cpu会发出一个指令,让其他核心L1 Cache中保存的值变成失效状态,当其他核心需要读取这个值时,需要到主存中重新加载。
1.2、工作内存与主存的一致性
若共享变量被定义为volatile,则:
写一个volatile变量时,JMM会把线程对应的工作内存中的变量值刷新到主存。
读一个volatile变量时,JMM会把线程对应的本地内存置为无效,然后从主存中读取该变量。
二、指令重排
是否允许重排序 | 第二个操作 | 第二个操作 | 第二个操作 |
---|---|---|---|
第一个操作 | 普通读/写 | volatile读 | volatile写 |
普通读写 | 是 | 是 | 否 |
volatile读 | 否 | 否 | 否 |
volatile写 | 是 | 否 | 否 |
可以看出:
1、当第二个操作是volatile写时,不论第一个操作是什么,都不允许重排。
2、当第一个操作是volatile读时,不论第二个操作是什么,都不允许重排。
3、当第一个操作是volatile写,第二个操作是volatile读时,不允许重排。
Java编译器通过插入内存屏障来实现以上规则。JMM内存屏障分为四种:
屏障类型 指令示例 说明
LoadLoad Barriers Load1; LoadLoad; Load2 确保load1先于load2
StoreStore Barriers Store1; StoreStore; Store2 确保store1先于store2
LoadStore Barriers Load1; LoadStore; Store2 确保load1先于Store2
StoreLoad Barriers Store1; StoreLoad; Load2 确保store1先于load2
采用以下策略进行内存屏障插入:
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的前面插入一个Load Load屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。