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,提高工作效率。

如何在多个Java应用程序之间共享连接池 java多进程共享内存_主存

如何在多个Java应用程序之间共享连接池 java多进程共享内存_java_02

解决方法

变量前面加上关键字volatile

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存


代码块加synchronized也是可以解决可见性问题但是比较重量级


可见性 vs 原子性



前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况


终止模式之两阶段终止模式 

5.3 有序性

JVM会在不影响正确性的前提下,可以调整语句的执行顺序

如何在多个Java应用程序之间共享连接池 java多进程共享内存_开发语言_03

每个指令都可以分为:取指令,指令译码,执行指令,内存访问,数据写回这5个阶段。现代cpu支持多级指令流水线,可以同时执行上述的五个阶段,这时cpu可以同时运行五条指令的不同阶段,相当于一条执行时间最长的复杂指令,本质上不能缩短单条指令的执行时间,但是它变相提高了指令的吞吐量。

指令的重排序的前提是不能影响结果

解决方法

在变量前加上关键字volatile

volatile的底层实现原理是内存屏障,Memory Barrier

  1. 对volatile变量的写指令后会加入写屏障
  2. 对volatile变量的读指令前会加入读屏障

如何保证可见性

写屏障就是写入该变量,就把他这一行包括前面所有变量都同步到主存中

读屏障就是保证从该变量读取代码之后是加载主存的信息

如何在多个Java应用程序之间共享连接池 java多进程共享内存_开发语言_04

如何在多个Java应用程序之间共享连接池 java多进程共享内存_主存_05

 

 如何保证有序性

如何在多个Java应用程序之间共享连接池 java多进程共享内存_缓存_06

如何在多个Java应用程序之间共享连接池 java多进程共享内存_缓存_07

 double-checked locking

如何在多个Java应用程序之间共享连接池 java多进程共享内存_主存_08

 但是在多线程的环境下,以上代码是不正确的,可能发生指令重排。

首先在字节码上分析,一个对象可以不用调用构造方法就赋值到变量,在上图代码中,如果一个线程只执行了赋值操作,但是对象没有调用构造方法,第二个线程突然判断是否为空,此时是不为空的,就返回一个兑现,这个时候这个对象就是没有完成初始化的对象

那怎么解决?加volatile关键字,synchronized不能防止重排序(只能防止代码逻辑没错)