5.1 JAVA内存模型
JMM是Java Memory Model, 它定义了主存,工作内存抽象概念,底层对应着CPU寄存器(缓存,硬件内存,CPU指令优化等)。
JMM体现在以下几个方面
原子性:保证指令不会受到线程上下文切换的影响
可见性:保证指令不会受cpu缓存的影响
有序性:保证指令不会受cpu指令并行优化的影响
5.2 可见性
循环问题
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
sleep(1);
run = false; // 线程t不会如预想的停下来
}
当线程循环获取run值到一定次数 的时候,会优化,不再从主内存中获取数据,而是会开辟工作内存存储run,提高工作效率。
解决方法
变量前面加上关键字volatile
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
代码块加synchronized也是可以解决可见性问题但是比较重量级
可见性 vs 原子性
前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况
终止模式之两阶段终止模式
5.3 有序性
JVM会在不影响正确性的前提下,可以调整语句的执行顺序
每个指令都可以分为:取指令,指令译码,执行指令,内存访问,数据写回这5个阶段。现代cpu支持多级指令流水线,可以同时执行上述的五个阶段,这时cpu可以同时运行五条指令的不同阶段,相当于一条执行时间最长的复杂指令,本质上不能缩短单条指令的执行时间,但是它变相提高了指令的吞吐量。
指令的重排序的前提是不能影响结果
解决方法
在变量前加上关键字volatile
volatile的底层实现原理是内存屏障,Memory Barrier
- 对volatile变量的写指令后会加入写屏障
- 对volatile变量的读指令前会加入读屏障
如何保证可见性
写屏障就是写入该变量,就把他这一行包括前面所有变量都同步到主存中
读屏障就是保证从该变量读取代码之后是加载主存的信息
如何保证有序性
double-checked locking
但是在多线程的环境下,以上代码是不正确的,可能发生指令重排。
首先在字节码上分析,一个对象可以不用调用构造方法就赋值到变量,在上图代码中,如果一个线程只执行了赋值操作,但是对象没有调用构造方法,第二个线程突然判断是否为空,此时是不为空的,就返回一个兑现,这个时候这个对象就是没有完成初始化的对象
那怎么解决?加volatile关键字,synchronized不能防止重排序(只能防止代码逻辑没错)