1、Java 内存模型
主内存:类比Jvm 模型 相当于堆;
工作内存:线程独有的内存,类比处理器的高速缓存;
线程的「工作内存」保存了被当前线程使用到的变量的主内存的「副本拷贝」,线程对变量所有的操作(读取、赋值)都必须在「工作内存中」完成,不能直接读写主内存中的变量;不同的线程之间不能「直接」相互访问对方工作内存中的变量,线程之间的值传递需要通过主内存来完成;
线程-工作内存-主内存之间的关系如下:
2、内存间的交互操作
工作内存和主内存的交互主要这8个操作lock(锁定):作用于主内存的变量,它把变量标识为线程独占状态;
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,只有释放出来的变量才能被其他线程锁定;
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到工作内存中,便于下一个(load)操作;
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入到工作内存中的变量副本中;
use(使用):作用于工作内存的变量,它把工作内存中的变量的值传递给执行引擎;
assign(赋值):作用于工作内存的变量,它把一个执行引擎接收到的值赋给工作内存的变量;
store(存储):作用于工作内存变量,它把工作内存中的变量值传送到主内存中,便于下一个操作(write)使用;
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的值放入主内存变量中;
操作规则不允许 read和load ,store和write操作之一单独出现;
不允许一个线程丢弃它最近的assign操作,变量在工作内存中改变了之后必须把变化同步到主内存中;
不允许一个线程无原因的(不经过任何assign操作)把数据从工作内存同步回主内存中;
一个新的变量必须从主内存中“诞生”;
一个变量在同一时刻只允许一条线程对其lock操作,但是可以被lock多次,但是必须执行相同次数的unlock操作才可以被解锁;
如果对一个变量lock操作,那么会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值;
一个变量未执行lock,不允许对它执行unlock操作,也不允许去unlock一个被其他线程lock的对象;
对一个变量执行unlock前,必须将此变量的值同步回主内存中;
volatitle
一、保证可见性,并不能保证操作的原子性,使用volatitle需要满足下面的条件运算结果并不依赖变量的当前值,或者能确保只有单一的线程修改线程的变量值;
变量不需要与其他的状态变量共同参与不变约束;(个人理解是没有其他的变量 即 n=1,n=m+1这种操作,而不是n=n+1,这种n不确定状态的操作)
二、禁止指令重排
Java内存模型中对volatile变量定义的特殊规则。
假定T表示一个线程,V和W分别表示两个volatile型变量,那么在进行read、load、use、assign、store和write操作时需要满足如下规则:
只有当线程T对变量V执行的前一个动作是load的时候,线程T才能对变量V执行use动作;并且,只有当线程T对变量V执行的后一个动作是use的时候,线程T才能对变量V执行load动作。线程T对变量V的use动作可以认为是和线程T对变量V的load、read动作相关联,必须连续一起出现;(这条规则要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V所做的修改后的值)。
只有当线程T对变量V执行的前一个动作是assign的时候,线程T才能对变量V执行store动作;并且,只有当线程T对变量V执行的后一个动作是store的时候,线程T才能对变量V执行assign动作。线程T对变量V的assign动作可以认为是和线程T对变量V的store、write动作相关联,必须连续一起出现(这条规则要求在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改)。
假定动作A是线程T对变量V实施的use或assign动作,假定动作F是和动作A相关联的load或store动作,假定动作P是和动作F相应的对变量V的read或write动作;类似的,假定动作B是线程T对变量W实施的use或assign动作,假定动作G是和动作B相关联的load或store动作,假定动作Q是和动作G相应的对变量W的read或write动作。如果A先于B,那么P先于Q(这条规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同)。
JMM基于保守策略的JMM内存屏障插入策略:
1.在每个volatile写操作的前面插入一个StoreStore屏障
2.在每个volatile写操作的后面插入一个SotreLoad屏障
3.在每个volatile读操作的后面插入一个LoadLoad屏障
4.在每个volatile读操作的后面插入一个LoadStore屏障
实现规则
JMM对读写的重排序有以下规则,纵轴为操作1,横轴为操作2:
从表中可以看出,在第一个操作为volatile读的时候,其后的所有变量读写操作都不会重排序到前面。
在第二个操作为volatile读的时候,其之前的所有volatile读写操作都已完成,
在第一个操作为volatile写的时候,其后的volatile变量读写操作都不会重排序到前面。
可以得出以下内存屏障表格
根据JMM规则,结合内存屏障的相关分析,可以得出以下保守策略:volatile读之后,会添加LoadLoad内存屏障。
volatile读之后,会添加LoadStore内存屏障。
volatile写之前,会添加StoreStore内存屏障。
volatile写之后,会添加StoreLoad型内存屏障。