Java Memory Model(Java内存模型)
JMM定义了主存(所有线程都共享的数据)和工作内存(每个线程都私有的数据)抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等
体现如下:
- 原子性 - 保证指令不会受到上下文切换的影响
- Monitor主要关注的就是访问共享变量时,保证临界区代码的原子性
- 可见性 - 保证指令不会受到cpu缓存的影响
- 有序性 - 保证指令不会受到cpu执行并行优化的影响
可见性
一个线程对主存中的数据进行了修改,对于另一个线程不可见(可能是另一个线程有缓存)就导致了可见性问题
volatile(易变关键字)
它用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找该变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存,解决可见性问题
public volatile staic boolean flag1 = true;
private volatile boolean flag2 = true;
用synchronized加锁也可以解决可见性问题,但是volatile更轻量
有序性
JVM会在不影响正确性的前提下,可以调整语句的执行顺序
这种特性称为指令重排,多线程下的指令重排会影响正确性
volatile也可以解决重排序问题
volatile原理
volatile的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对volatile变量的写指令后会加入写屏障
- 对volatile变量的读指令前会加入读屏障
写屏障(sfence)保证在该屏障之后,对共享变量的改动,都同步到主存中(保证可见性)
还会确保指令重排时,不会将写屏障之前的代码排在写屏障之后(保证有序性)
volatile static boolean ready = false;
public void actor1(I_Result r){
num = 2;
ready = true; // ready是volatile赋值带写屏障
// 写屏障
}
读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中的最新数据(保证可见性 )
还会确保指令重排时,不会将读屏障之后的代码排在读屏障之前(保证有序性)
public void actor2(I_Result r){
// 读屏障
if(ready){// ready是volatile修饰带读屏障
r.r1 = num + num;
}else{
r.r1 = 1;
}
}